
September 09, 2026
14 min read
By Kokil Thapa | Last reviewed: September 2026
Hard-coded API keys in .env files and plaintext credentials in Git repos cause most production breaches I see on client projects. Secrets Management with HashiCorp Vault gives you a single control plane for passwords, tokens, certificates, and encryption keys. Applications fetch what they need at runtime instead of storing long-lived secrets on disk. This guide covers a practical Vault setup for teams running Linux system administration, Laravel apps, and CI/CD pipelines in 2026.
.env files with audited, revocable access.What is Secrets Management with HashiCorp Vault and why does it matter?
Vault is an identity-aware secrets store from HashiCorp. It encrypts data at rest, controls who can read or write each path, and logs every access attempt. You stop copying the same database password into five servers and three developer laptops.
On production Laravel applications I maintain, the pattern is familiar. A payment gateway key lives in .env on the web server. A cron job on another host uses a copy. A contractor gets a third copy over Slack. Rotation means editing multiple files and hoping nothing breaks at 2 a.m.
Vault replaces that sprawl with one source of truth. Your app authenticates, receives a token with limited scope, reads the secret, and the token expires. If a laptop is lost, you revoke one policy instead of rotating every credential manually.
Vault supports several secret engines. The KV (key-value) engine stores static secrets like SMTP passwords. The database engine creates temporary MySQL or PostgreSQL users with a TTL you define. The PKI engine issues TLS certificates on demand. Transit encrypts data without exposing the master key to your application code.
For enterprise application development teams in Nepal and abroad, the audit log alone justifies the effort. Every read and write is recorded with the caller identity, IP, and timestamp. That matters when you handle client documents on legal-tech portals or payment credentials on eCommerce sites.
Core concepts you must understand first
- Mount path: Each engine lives at a path like
secret/ordatabase/. - Policy: HCL rules that grant read, write, or list on specific paths.
- Auth method: How a client proves identity—AppRole, JWT, userpass, or cloud IAM.
- Token: Short-lived credential Vault returns after successful auth.
- Seal: Encryption barrier; unseal keys or auto-unseal via cloud KMS opens it.
How do you install and configure HashiCorp Vault for production?
Start with a dedicated server or a three-node cluster for high availability. Vault runs fine on Ubuntu 24.04 alongside your existing stack. Do not co-locate it on the same VM as your public web app.
Install from the official HashiCorp repository. Pin a version in your deployment notes so upgrades are deliberate, not accidental package updates.
# Ubuntu 24.04 — add HashiCorp repo and install Vault
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install vault
# Verify binary
vault version Production server configuration
Create /etc/vault.d/vault.hcl with TLS enabled from day one. Self-signed certs work for internal traffic. Use Let's Encrypt or a proper CA for anything crossing networks you do not fully control.
storage "raft" {
path = "/opt/vault/data"
node_id = "vault-1"
}
listener "tcp" {
address = "0.0.0.0:8200"
tls_cert_file = "/etc/vault.d/tls/vault.crt"
tls_key_file = "/etc/vault.d/tls/vault.key"
}
api_addr = "https://vault.example.com:8200"
cluster_addr = "https://vault.example.com:8201"
ui = true
disable_mlock = true Initialize once, save the unseal keys and root token offline, then unseal each node. For production, enable auto-unseal via cloud KMS so a reboot does not require three people with key shards.
- Run
vault operator init -key-shares=5 -key-threshold=3on the first node. - Store unseal keys in separate physical or password-manager locations.
- Unseal with
vault operator unsealuntil sealed status is false. - Enable audit logging to a append-only file or syslog sink immediately.
- Revoke or rotate the root token after creating an admin policy.
I have seen teams skip audit logging during the "pilot phase" and never turn it on. Treat audit as mandatory from the first secret you store. The same applies to secrets scanning in Git and CI—both layers catch different failure modes.
How do you store, rotate, and retrieve secrets in Vault?
Enable the KV v2 engine at a path your team owns. Version 2 adds check-and-set, soft delete, and metadata—use it unless you have a legacy reason not to.
# Enable KV v2 at secret/
vault secrets enable -path=secret kv-v2
# Write a secret
vault kv put secret/laravel/production \
DB_PASSWORD='strong-random-value' \
STRIPE_SECRET='sk_live_...'
# Read it back
vault kv get -format=json secret/laravel/production Writing policies that follow least privilege
Never give an application broad secret/* read access. Scope each policy to one environment and one service.
# /etc/vault.d/policies/laravel-production.hcl
path "secret/data/laravel/production" {
capabilities = ["read"]
}
path "database/creds/laravel-readonly" {
capabilities = ["read"]
} Apply the policy and bind it to an AppRole your Laravel app uses at boot:
vault policy write laravel-production /etc/vault.d/policies/laravel-production.hcl
vault auth enable approle
vault write auth/approle/role/laravel-production \
token_policies="laravel-production" \
token_ttl=1h \
token_max_ttl=4h Dynamic database credentials
Static database passwords are the biggest win Vault offers for Laravel teams on MySQL 9.7 or PostgreSQL 18. Vault creates a user, grants the role you define, and deletes the user when the TTL expires.
vault secrets enable database
vault write database/config/mysql-prod \
plugin_name=mysql-database-plugin \
connection_url="{{username}}:{{password}}@tcp(127.0.0.1:3306)/" \
allowed_roles="laravel-readonly" \
username="vault_admin" \
password="admin-bootstrap-only"
vault write database/roles/laravel-readonly \
db_name=mysql-prod \
creation_statements="CREATE USER '{{name}}'@'%' IDENTIFIED BY '{{password}}'; GRANT SELECT ON app_db.* TO '{{name}}'@'%';" \
default_ttl="1h" \
max_ttl="24h" Your app calls vault read database/creds/laravel-readonly at request time or caches credentials until five minutes before expiry. Read the dedicated guide on Vault dynamic secrets for databases for rotation edge cases.
For encryption without storing keys in PHP, enable the Transit engine. Laravel encrypts a payload via Vault's API and stores only ciphertext in the database. That pattern works well for PII fields on client portals like those described in our Mijar Law Associates portfolio case.
How do Laravel apps and CI/CD pipelines integrate with Vault?
Laravel 12 and 13 still read .env by default. Vault does not replace .env overnight. You migrate high-risk values first: payment keys, SMS API tokens, and database passwords.
Option A: Vault Agent sidecar (recommended)
Vault Agent runs beside your app. It authenticates via AppRole, renders secrets into a file, and renews the token automatically. Laravel reads a generated .env.vault that never enters Git.
# /etc/vault.d/agent-laravel.hcl
pid_file = "/tmp/vault-agent.pid"
auto_auth {
method {
type = "approle"
config = {
role_id_file_path = "/etc/vault/role-id"
secret_id_file_path = "/etc/vault/secret-id"
}
}
}
template {
source = "/etc/vault/templates/laravel.env.tpl"
destination = "/var/www/app/shared/.env.vault"
command = "systemctl reload php8.3-fpm"
} The template file maps Vault paths to Laravel env keys:
{{ with secret "secret/data/laravel/production" }}
DB_PASSWORD={{ .Data.data.DB_PASSWORD }}
STRIPE_SECRET={{ .Data.data.STRIPE_SECRET }}
{{ end }} On Deployer 7 releases I use for sister legal-tech sites, the shared directory persists across symlink swaps. Vault Agent writes there; each new release inherits the same secrets file without manual copy steps. See CI/CD secrets management best practices for pipeline-specific patterns.
Option B: Direct API call at bootstrap
For queue workers or one-off Artisan commands, authenticate once in a service provider and bind values into the config cache:
/* app/Providers/VaultServiceProvider.php — simplified */
$response = Http::post("{$vaultAddr}/v1/auth/approle/login", [
'role_id' => config('vault.role_id'),
'secret_id' => config('vault.secret_id'),
]);
$token = $response->json('auth.client_token');
$secret = Http::withToken($token)
->get("{$vaultAddr}/v1/secret/data/laravel/production")
->json('data.data');
config(['database.connections.mysql.password' => $secret['DB_PASSWORD']]); Cache the token until renewal is needed. Do not call Vault on every HTTP request—that adds latency and audit noise.
CI/CD integration
GitLab CI can authenticate with JWT/OIDC if your Vault trust is configured. The pipeline receives a short-lived token, reads deployment keys, and never stores them in CI variables long term.
# .gitlab-ci.yml excerpt
deploy:
id_tokens:
VAULT_ID_TOKEN:
aud: https://vault.example.com
script:
- export VAULT_TOKEN=$(vault write -field=token auth/jwt/login role=gitlab-deploy jwt=$VAULT_ID_TOKEN)
- vault kv get -field=SSH_KEY secret/deploy/production > deploy_key
- dep deploy production Pair this with Ansible Vault for secrets only where Ansible still manages host-level config. Vault owns runtime secrets; Ansible Vault can encrypt inventory vars that bootstrap Vault itself—a narrow, intentional overlap.
How does HashiCorp Vault compare to AWS Secrets Manager and Ansible Vault?
Teams often ask which tool to adopt first. The answer depends on where your apps run and who operates the infrastructure.
| Criteria | HashiCorp Vault | AWS Secrets Manager | Ansible Vault |
|---|---|---|---|
| Best for | Multi-cloud, on-prem, Kubernetes | AWS-native workloads only | Encrypting Ansible vars at rest |
| Dynamic DB creds | Yes — MySQL, PostgreSQL, MongoDB | Yes — RDS integration | No — static encryption only |
| Audit logging | Built-in, exportable | CloudTrail integration | None by default |
| Self-hosted option | Yes — full control | No — managed AWS service | Yes — local encrypt/decrypt |
| Operational cost | Server + HA cluster (~Rs 8,000–15,000/mo VPS, ~USD 60–110) | Per-secret monthly fee | Free — CLI only |
| Laravel on Ubuntu VPS | Excellent fit | Requires AWS SDK + IAM | Deploy-time only, not runtime |
If everything already lives in AWS, AWS Secrets Manager is simpler to adopt. If you run Laravel on Ubuntu VPS instances—the setup I use for most Nepal client projects—Vault on the same infrastructure or a adjacent management subnet is the natural choice.
Ansible Vault encrypts files in your repo. It does not serve secrets to a running PHP-FPM process. Use both: Ansible Vault for bootstrap secrets that install Vault itself; Vault for everything the application needs at runtime.
For Kubernetes workloads, pair Vault with the External Secrets Operator so pods mount secrets as native Kubernetes secrets without hard-coding Vault tokens in manifests. Read Kubernetes secrets management done right for the full picture.
Common production mistakes
- Running a single unsealed node with no backup of the raft storage path.
- Using the root token in CI pipelines instead of scoped roles.
- Granting
sudopolicies that includesecret/*read on all paths. - Storing unseal keys in the same password manager as application passwords.
- Skipping TLS because "it is internal traffic only."
Generate strong bootstrap passwords with a local password generator during init. Never reuse a password you can remember—that defeats the purpose of a secrets store.
Multi-cloud teams should read multi-cloud secrets management before picking a primary store. Vault often acts as the neutral layer above vendor-specific services.
Protecting secrets in AI integrations is a growing concern. If your Laravel app sends prompts to external LLM APIs, treat those keys like payment credentials. The guide on protecting PII and secrets in LLM apps covers redaction patterns that complement Vault's access controls.
For ongoing operations, fold Vault health checks into your support and maintenance runbook: sealed status, raft peer count, audit device errors, and certificate expiry on the listener.
The official HashiCorp Vault tutorials remain the best reference for engine-specific configuration. Cross-check security assumptions against the OWASP Secrets Management project checklist before go-live.
Key Takeaways
- Enable KV v2, database, and audit logging before storing your first production secret.
- Scope policies per app and environment—never grant blanket
secret/*read access. - Use AppRole or JWT auth for Laravel and CI; rotate role/secret IDs on a schedule.
- Prefer dynamic database credentials with one-hour TTL over static passwords in
.env. - Run a three-node Raft cluster with auto-unseal and tested backups, not a single dev-mode instance.
- Migrate incrementally: payment keys and database passwords first, then SMTP and third-party API tokens.
People Also Ask
Is HashiCorp Vault free for production use?
Vault Community Edition is free and open source under the BSL license. It includes KV, database, PKI, Transit, and AppRole engines sufficient for most Laravel VPS deployments. HashiCorp sells Enterprise for HSM integration, performance replication, and namespaces. Start with Community; upgrade only when a specific Enterprise feature blocks you.
Can Vault replace my Laravel .env file completely?
Not on day one. Laravel expects environment variables at bootstrap. Vault Agent can render a local env file or you can inject config in a service provider. Low-risk values like APP_DEBUG=false can stay in static env files. Move payment keys, database passwords, and signing secrets to Vault first.
How do you back up HashiCorp Vault data?
Back up the raft storage directory with filesystem snapshots while Vault is running—raft supports consistent snapshots via vault operator raft snapshot save. Store snapshots encrypted off-site. Test restore on a staging cluster quarterly. Backing up only the config files without raft data loses every secret.
What happens if Vault goes down?
Applications with cached credentials keep running until TTL expiry. Plan for Vault downtime by setting reasonable token and dynamic credential TTLs, running HA with three nodes, and monitoring sealed state. A sealed cluster rejects all reads—treat unseal failures as a P1 incident with the same urgency as database outage.
Ship Secrets Management with HashiCorp Vault the right way
Static credentials in Git and shared .env files fail quietly until they fail loudly. Secrets Management with HashiCorp Vault gives you audited, revocable, short-lived access that scales from a single Laravel VPS to a multi-service cluster. Start with one AppRole, one KV path, and one dynamic database role. Expand from there as your custom software footprint grows.
If you want help designing Vault policies, wiring Deployer 7 releases, or migrating payment keys off plaintext env files, contact us for a scoped review. You can also browse the about me page for background on production deployments across Nepal legal-tech and eCommerce platforms, or explore related posts on the blog for deeper CI and infrastructure guides.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

