Compare secrets management solutions for enterprise applications. Covers HashiCorp Vault, AWS Secrets Manager, Azure Key Vault, and implementation patterns for application secrets, API keys, and certificates.
Secrets management is the practice of storing, retrieving, and rotating sensitive credentials, tokens, keys, and configuration values across systems and environments. It matters when a service needs to authenticate to databases, cloud APIs, third-party services, or other microservices—especially when those credentials must be dynamic, secure, and auditable. It is essential for any system with more than a handful of credentials, particularly in multi-environment, multi-team, or cloud-native deployments.
Secrets are stored in a central vault, typically encrypted at rest and in transit, with access controlled via identity and policy. A secret is a key-value pair with metadata: name, value, version, created, updated, and optional description. The value may be a raw string, JSON, or binary blob. Retrieval is typically a simple GET /secrets/{name} request over HTTPS, using a token or certificate for authentication.
To retrieve a secret, a service sends a request to the secrets backend:
GET /secrets/database/password HTTP/1.1
Host: secrets.example.com
Authorization: Bearer a1b2c3d4e5f6
Accept: application/json
A successful response returns the secret value and metadata:
{
"name": "database/password",
"value": "s3cr3t_p4ssw0rd",
"version": 3,
"created": "2023-09-15T12:00:00Z",
"updated": "2023-09-20T08:30:00Z",
"description": "Password for production PostgreSQL instance"
}
If the secret does not exist, the service receives a 404 Not Found. If the token is expired or invalid, a 401 Unauthorized is returned with WWW-Authenticate: Bearer error="invalid_token", error_description="Token expired".
Secrets are typically stored in a hierarchical namespace. The database/password secret is stored under a database/ prefix. A service may retrieve a full subtree with a single request:
GET /secrets/database HTTP/1.1
Host: secrets.example.com
Authorization: Bearer a1b2c3d4e5f6
This returns:
{
"database": {
"password": "s3cr3t_p4ssw0rd",
"host": "db.prod.example.com",
"port": 5432
}
}
The response includes all secrets under the database/ path. The server may cache the tree structure and return a ETag header to support conditional requests.
Secrets can be dynamically generated and issued with a lease period. A lease is a time-bound access to a secret, after which the secret becomes invalid or is automatically renewed.
To obtain a dynamic secret, a service sends a POST request to a secret endpoint with parameters:
POST /secrets/database/roles/dev HTTP/1.1
Host: secrets.example.com
Authorization: Bearer a1b2c3d4e5f6
Content-Type: application/json
{
"ttl": "1h",
"db_name": "myapp_dev"
}
The backend creates a temporary database user with a unique username and password, configured for the specified db_name, and returns the credentials:
{
"name": "database/roles/dev",
"value": "user:dev-12345",
"lease_id": "db-12345",
"lease_duration": 3600,
"renewable": true,
"data": {
"username": "dev-12345",
"password": "p4ssw0rd-12345",
"host": "db.prod.example.com",
"port": 5432
}
}
The service uses the username and password to connect to the database. The secret is valid for one hour. The service may renew the lease by calling:
PUT /secrets/leases/renew HTTP/1.1
Host: secrets.example.com
Authorization: Bearer a1b2c3d4e5f6
Content-Type: application/json
{
"lease_id": "db-12345",
"increment": 1800
}
If the lease is renewed successfully, the response includes the updated lease_duration and renewed_at. If the lease is expired or invalid, a 400 Bad Request with {"error": "lease_not_found"} is returned.
Renewal can be automated via periodic polling or a callback. The service may register a renewal callback URL with the secrets backend:
{
"callback": "https://app.example.com/renewal/callback",
"renewal_interval": 300,
"max_renewals": 10
}
On each renewal, the secrets backend calls the callback with the lease_id and updates the lease state. If the callback fails, the backend retries up to three times with exponential backoff.
Secrets must be rotated regularly to reduce the window of compromise. Rotation is the process of updating a secret’s value and making it available under a new version, while keeping the old version active.
A rotation is initiated by a service or a scheduler. The process involves:
To rotate a secret, a service sends a PUT request with the new value and metadata:
PUT /secrets/database/password HTTP/1.1
Host: secrets.example.com
Authorization: Bearer a1b2c3d4e5f6
Content-Type: application/json
{
"value": "n3w_s3cr3t_p4ssw0rd",
"version": 4,
"description": "Rotated for Q4 security audit",
"rotation": {
"started": "2023-09-22T09:00:00Z",
"completed": "2023-09-22T09:15:00Z",
"old_version": 3,
"new_version": 4
}
}
The backend stores the new version, marks the previous version as deprecated, and updates the metadata. Consumers may retrieve the current version with ?version=latest or ?version=4.
Rotation may be automatic, triggered by a cron job or event. A common pattern is to use a rotate endpoint that generates a new secret and updates the store atomically:
POST /secrets/database/password/rotate HTTP/1.1
Host: secrets.example.com
Authorization: Bearer a1b2c3d4e5f6
This endpoint generates a new value, assigns a new version, and returns the full secret object with the new value and metadata. The caller can use the new credentials immediately.
Access to secrets is governed by policies that define which identities can read, write, or manage secrets. Policies are named, hierarchical, and applied at multiple levels: global, namespace, and individual secret.
A policy defines rules such as:
read on database/*create on secrets/api-keyslist on secrets/*/roles/*Policies are applied to identities such as users, service accounts, or application roles. An identity may have multiple policies, and the effective policy is the union of all applied rules.
A service authenticates using a token issued by the secrets backend. The token includes claims such as sub, iss, exp, scope, and roles.
{
"sub": "app:web-server",
"iss": "secrets.example.com",
"exp": 1695432000,
"scope": "read:secrets,write:secrets",
"roles": [
"web-server",
"production"
]
}
The secrets backend validates the token and checks the policy. For example, a service with roles: [web-server, production] may read database/password but not secrets/api-keys, because the web-server role grants read:database/*, and production grants write:secrets/*, but the api-keys path is not covered.
The backend returns a 403 Forbidden if access is denied, with a detailed error:
{
"error": "access_denied",
"message": "Insufficient permissions to access secrets/api-keys",
"details": {
"required_scopes": ["read:secrets"],
"missing_scopes": ["read:secrets/api-keys"],
"policies": [
"web-server",
"production"
],
"allowed_paths": [
"database/*",
"secrets/*"
]
}
}
Policies are versioned and may be updated without affecting existing secrets. A policy is stored as a JSON object with a name, description, rules, and version.
{
"name": "web-server",
"description": "Access to web server secrets",
"version": 2,
"rules": [
{
"path": "database/*",
"actions": ["read", "list"]
},
{
"path": "secrets/api-keys/*",
"actions": ["read", "write", "create"]
}
]
}
When a policy is updated, the backend may keep the previous version and apply the new rules to new requests. A service may specify which policy version to use:
GET /secrets/database/password HTTP/1.1
Host: secrets.example.com
Authorization: Bearer a1b2c3d4e5f6
X-Policy-Version: 2
The backend applies the web-server policy version 2 to the request.
Applications retrieve secrets at startup or during runtime. The most common pattern is to read secrets from environment variables. A service may start with the following environment:
export DATABASE_PASSWORD=$(curl -s -H "Authorization: Bearer $(cat /run/secrets/token)" \
http://secrets.example.com/secrets/database/password | jq -r '.value')
export API_KEY=$(curl -s -H "Authorization: Bearer $(cat /run/secrets/token)" \
http://secrets.example.com/secrets/api-keys/webapp | jq -r '.value')
Alternatively, secrets are mounted as files. The secrets backend may provide a mount endpoint that generates a tarball of all needed secrets:
GET /secrets/mount/web-server?format=tar&include=database,password,api-keys HTTP/1.1
Host: secrets.example.com
Authorization: Bearer a1b2c3d4e5f6
The response is a binary application/x-tar stream containing files:
secrets/
├── database/password
├── database/host
├── api-keys/webapp
└── api-keys/admin
The application can mount this tarball as a volume and read secrets from the secrets/ directory. The file system is the primary source of secrets, and updates are synchronized via a polling or event-driven mechanism.
401 and the service must refresh the token or retry the request.read on secrets/api-keys, another grants write on secrets/api-keys/*. The effective policy is the union, but a service may assume a strict hierarchy.404 Not Found for a new secret that is not yet written.Secrets backends are configured using a standard configuration file, often in JSON or YAML, with keys such as:
secrets:
backend:
type: "vault"
url: "https://secrets.example.com"
token: "a1b2c3d4e5f6"
timeout: 30
retries: 3
caching:
enabled: true
ttl: 300
max_size: 1000
rotation:
interval: "1h"
strategy: "rolling"
callback: "https://app.example.com/rotate"
policies:
- name: "web-server"
version: 2
rules:
- path: "database/*"
actions: ["read", "list"]
- path: "secrets/api-keys/*"
actions: ["read", "write", "create"]
The configuration is loaded at startup and may be reloaded dynamically via a SIGHUP signal or HTTP endpoint.
The backend may expose a health endpoint:
GET /health HTTP/1.1
Host: secrets.example.com
A successful response returns 200 OK with a JSON body:
{
"status": "healthy",
"uptime": 18472,
"version": "1.2.3",
"backend": {
"type": "vault",
"status": "online",
"connected": true
},
"cache": {
"hits": 452,
"misses": 37,
"size": 921,
"max_size": 1000
}
}
A 503 Service Unavailable indicates the backend is down. The service may fall back to a local cache or use a default secret.
This page was rewritten on 16 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy