Vault PKI Secrets Engine in Production: Intermediate CAs, cert-manager, and Short-Lived Certificates as a Security Primitive
Modern service infrastructure lives or dies by its certificate hygiene. If you are still handing out one-year certificates with a vague promise to check the OCSP responder, this post is for you. We will walk through HashiCorp Vault’s PKI secrets engine — from the philosophical shift that short-lived certificates represent, through every vault write command needed to stand up a production two-tier CA hierarchy, to cert-manager integration, Vault Agent sidecar patterns, SPIFFE identity, and the operational monitoring that keeps it all honest.
Versions current as of this writing: Vault 2.0.1 (released May 2025, the first release under IBM’s major-version lifecycle policy — PKI secrets engine behavior is unchanged from the 1.x line), cert-manager 1.20.0 (March 2026).
1. Why Short-Lived Certificates Are a Security Primitive
The Revocation Problem Is Real and Largely Unsolved
The traditional X.509 revocation story has two mechanisms: Certificate Revocation Lists (CRLs) and the Online Certificate Status Protocol (OCSP). Both have deep structural problems in practice.
CRLs are files signed by the CA listing revoked serial numbers. Browsers and TLS libraries download them, cache them for the stated “next update” window (often 24–72 hours), and check locally. This means a revoked certificate remains usable for the entire cache window after revocation — potentially days. For a compromised service credential in your infrastructure, that window is unacceptable.
OCSP was designed to fix CRL latency by enabling real-time per-certificate status checks. In the web PKI it largely failed for three structural reasons:
- Soft-fail: Most TLS implementations treat an OCSP timeout as “valid.” An attacker who blocks the OCSP endpoint gets the certificate treated as good indefinitely.
- Privacy: Every OCSP check tells the CA exactly which certificate a user is connecting to. For public web PKI this leaked browsing behavior. For private PKI this is less critical but still an information leak.
- Availability: The OCSP responder becomes a hard dependency in your TLS handshake path. Outages in your certificate infrastructure cascade directly to service connectivity.
OCSP Stapling improves matters — the server pre-fetches its own status and staples it to the TLS handshake — but it requires server-side configuration and the staple itself has a validity window that reintroduces the latency problem.
The Mental Model Shift: Expiry Over Revocation
The short-lived certificate model abandons the “revoke when compromised” mental model entirely. The new model is: certs expire before an attacker can exploit them.
If a certificate is valid for 24 hours, a stolen private key can be abused for at most 24 hours. If the certificate is valid for 6 hours, the window is 6 hours. This bounds the blast radius of any key compromise without requiring any revocation infrastructure to function correctly.
The CA/B Forum Baseline Requirements (as of March 2026) formally recognize this: certificates with a validity period of 7 days or fewer are classified as “short-lived” and are exempt from CRL and OCSP requirements. The standards body acknowledges that expiry is a better revocation mechanism for short-lived credentials.
For internal service-to-service communication — mTLS between microservices, Kubernetes workload identity, API-to-API authentication — this is the correct model. Issue 24-hour certificates. Rotate automatically. Never build revocation infrastructure for these certs. The compromise window is shorter than most incident response cycles anyway.
The comparison that matters: A 1-year certificate with OCSP that an attacker has exploited is protected only by infrastructure that can fail silently. A 24-hour certificate with no revocation infrastructure is protected by physics — the cert stops working in 24 hours regardless of what the attacker does.
mTLS for Service Identity
Short-lived certificates become transformative when paired with mutual TLS. In mTLS, both client and server present certificates, establishing cryptographic proof of identity in both directions. Combined with short TTLs:
- Each service instance has a unique, short-lived identity certificate.
- Compromise of one instance’s key is temporally bounded.
- Identity is cryptographic, not network-position-based (no more “trust everything from the internal network” assumptions).
- Certificate metadata (subject, SANs, issuing CA) carries verifiable identity information usable for authorization decisions.
Vault’s PKI secrets engine is purpose-built for this use case: high-throughput issuance of short-lived certificates through an authenticated API.
2. Vault PKI Secrets Engine Architecture
Enabling a PKI Mount
Vault organizes secrets engines at mount paths. A PKI mount is an isolated certificate authority — it holds a CA certificate and private key, configuration, roles, and issued certificate records. You can have multiple PKI mounts, each representing a different CA or a different level of the hierarchy.
|
|
The max-lease-ttl on the mount is a hard cap — no certificate issued from this mount can exceed it. This is a safety control.
Root CA vs. Intermediate CA: The Two-Tier Model
The canonical PKI hierarchy for Vault production use has three levels:
Offline Root CA (external, never in Vault)
|
v
Vault Intermediate CA (pki_int/ mount)
|
v
Leaf Certificates (issued to services, max TTL hours to days)
Why you never put your root CA private key in Vault: The root CA is the ultimate trust anchor. Compromise of the root private key invalidates your entire PKI — every certificate issued, every trust relationship. The correct approach is to generate the root CA offline (air-gapped), store the private key in a hardware security module or encrypted offline storage, and only bring it online to sign the intermediate CA certificate (an infrequent operation). Vault never sees the root private key.
The intermediate CA lives in Vault. Its private key is generated inside Vault’s seal, protected by Vault’s unsealing mechanism (AWS KMS, Azure Key Vault, GCP CKMS, or Shamir seal). The intermediate CA’s private key performs the high-throughput day-to-day signing of leaf certificates. If the intermediate is compromised, you revoke it at the root (a rare, high-impact operation) and re-issue a new intermediate. The root’s isolation is preserved.
Multiple Issuers per Mount (Vault 1.11+)
Since Vault 1.11.0, a single PKI mount can contain multiple issuer certificates — different CA certs backed by different (or the same) key material. This is the mechanism for CA rotation without downtime: you add the new intermediate as an additional issuer, update roles to use it, and let old certificates expire naturally. Both issuers can sign certificates simultaneously during the transition window.
3. Full Two-Tier CA Setup: Step by Step
Step 1: Root CA (Offline or Vault-internal for labs)
Production: Use OpenSSL, CFSSL, or certstrap to generate an offline root. We show that path further below with the external root workflow.
Lab/staging: Generate a self-signed root directly in Vault.
|
|
Step 2: Intermediate CA in Vault
|
|
Step 3: Sign the Intermediate with the Root
If your root is in Vault (lab path):
|
|
If your root is an external/offline root (production path):
|
|
Step 4: Import the Signed Intermediate into Vault
|
|
Step 5: Configure Intermediate CA URLs
|
|
Step 6: Configure CRL Behavior
|
|
On CRL for short-lived certificates: If your maximum certificate TTL is 24 hours, the case for maintaining revocation infrastructure weakens considerably. A revoked 24-hour cert will expire before most CRL caches would have refreshed anyway. For pure service mesh / mTLS use cases where all certs are sub-24h, you can set disable=true in the CRL config. The trade-off: you lose the ability to do emergency revocation, but you gain operational simplicity and eliminate the CRL as a failure point. Make this decision explicitly, per environment.
Step 7: Enable Auto-Tidy
The tidy operation cleans up expired certificates from storage and rebuilds the CRL to remove entries for already-expired certs. Without auto-tidy, your storage fills with expired cert records and your CRL grows unboundedly.
|
|
You can also trigger tidy manually:
|
|
4. PKI Roles: The Policy Layer for Certificate Issuance
Vault PKI roles define the policy for certificate issuance. Every vault write pki_int/issue/<role-name> call is validated against the matching role. Roles are the primary access control surface between “authenticated Vault client” and “certificate with these properties.”
Role Reference
|
|
Field-by-field breakdown of the important ones:
| Field | What it does |
|---|---|
allowed_domains |
Comma-separated list. CNs and DNS SANs must match one of these domains (or a subdomain if allow_subdomains=true). |
allow_subdomains |
Whether *.allowed_domain matches. Enables foo.svc.cluster.local when svc.cluster.local is allowed. |
allow_glob_domains |
Whether glob patterns (e.g., *.*.example.com) are accepted in domain constraints. |
allow_bare_domains |
Whether the exact domain itself (not just a subdomain) is issuable as CN. |
allow_wildcard_certificates |
Whether *.example.com can appear as a SAN. Off by default; rarely needed for service identity. |
allowed_uri_sans |
Patterns for allowed URI SANs. Critical for SPIFFE: set to spiffe://your-trust-domain/*. Supports glob matching. |
allowed_uri_sans_template |
Whether Vault identity entity metadata can be interpolated into URI SANs (enables per-entity SPIFFE IDs). |
allow_ip_sans |
Whether IP addresses may appear as SANs. |
key_type |
rsa, ec, or ed25519. Ed25519 is fastest for signing but has less ecosystem support. EC P-256 is the practical sweet spot. RSA is required for some legacy systems. |
key_bits |
Depends on key_type: 2048/3072/4096 for RSA; 224/256/384/521 for EC; 0 for Ed25519. |
ttl |
Default certificate lifetime if not specified at issuance time. |
max_ttl |
Hard cap on certificate lifetime for this role. Cannot exceed the mount’s max-lease-ttl. |
require_cn |
Whether a Common Name must be provided. Set false for SPIFFE-only certs where CN is irrelevant. |
enforce_hostnames |
Whether CN and DNS SANs must be valid hostnames. |
server_flag |
Whether the Extended Key Usage: TLS Web Server Authentication OID is set. |
client_flag |
Whether the Extended Key Usage: TLS Web Client Authentication OID is set. |
code_signing_flag |
Whether the Code Signing EKU is set. Needed for artifact signing use cases. |
generate_lease |
Set to false for PKI roles. When true, Vault creates a lease record for every issued certificate — filling Vault storage with entries that serve no purpose (PKI certs are not revocable via lease expiry, only via the revoke endpoint). For high-volume issuance this causes serious performance and storage problems. |
no_store |
When true, Vault does not store the issued certificate in its backend. Improves performance dramatically (P-256 throughput: 300k certs vs. 65k with storage). Certificates can still be revoked using their serial number from the audit log. Required for standby-node read scaling. |
The generate_lease=false + no_store=true combination is the recommended configuration for high-frequency service certificate issuance. Your audit log becomes the record of issuance; Vault storage is not the source of truth for which certs exist.
5. Issuing Certificates
CLI Issuance
|
|
Response fields:
|
|
certificate: The leaf certificate for the requester.issuing_ca: The intermediate CA certificate.ca_chain: Full chain from leaf to root (use this for TLS configuration that needs the full chain).private_key: Returned once and never stored by Vault. The requester must persist it.serial_number: Hex-encoded serial. Use this for revocation.expiration: Unix timestamp. Useful for automation to know when to renew.
API Issuance
|
|
The sign vs. sign-verbatim Endpoints
When callers generate their own key pair and submit a CSR instead of having Vault generate the key:
pki_int/sign/<role-name> — The CSR subject and SANs are validated against the role policy. Use this for external CSRs that must still respect domain and SAN constraints. The role’s key type and bits are advisory when signing external CSRs.
pki_int/sign-verbatim — Accepts the CSR with minimal role validation. It will use the SANs from the CSR essentially as-is. Useful for one-off tooling but dangerous: it bypasses domain restrictions. Restrict access to this endpoint via Vault policy to specific break-glass use cases only. Note: As of Vault 2.0, sign-verbatim no longer ignores the BasicConstraints extension in CSRs — if isCA=true in the CSR, Vault now returns an error.
6. cert-manager Vault Issuer
cert-manager is the standard Kubernetes-native certificate lifecycle manager. Its Vault issuer integrates directly with the PKI secrets engine.
Vault Side: Auth and Policy Setup
cert-manager needs a Vault identity with permission to call the PKI signing endpoint. The recommended approach is Kubernetes auth — cert-manager authenticates to Vault using its Kubernetes service account token, which Vault validates via the Kubernetes TokenReview API.
|
|
ClusterIssuer YAML
|
|
For AppRole auth (useful for off-cluster tooling):
|
|
Create the AppRole secret:
|
|
Certificate Resource
|
|
The resulting api-service-tls-secret is a standard Kubernetes TLS secret with keys tls.crt, tls.key, and ca.crt. cert-manager will automatically renew it before renewBefore elapses.
cert-manager renewal timing: By default, cert-manager targets renewal at the 2/3 lifetime point. For a 24-hour certificate, renewal is triggered at hour 16. Setting renewBefore: 8h achieves the same: renewal triggered when 8 hours remain on a 24-hour cert.
7. Auto-Rotation Without cert-manager
Vault Agent Sidecar (Standalone)
For non-Kubernetes environments or cases where you want Vault Agent to manage certificates directly, use the template stanza with the pkiCert function.
Vault Agent configuration file (vault-agent.hcl):
|
|
The pkiCert function (introduced in Vault Agent as of Vault 1.11, preferred over the older secret function for PKI) fetches a new certificate on startup if none exists or if the existing one has expired. It tracks the certificate expiration and requests a renewal at roughly the 90% mark of the cert’s lifetime.
Vault Agent Injector in Kubernetes
The Vault Agent Injector operates as a mutating admission webhook. Annotate your Pod spec:
|
|
The injector adds an init container (runs before your app, ensures certs are present at startup) and a sidecar container (keeps running, renews certs before they expire). Application notification on renewal is handled via the command option in the template stanza — add it to the agent ConfigMap if you need SIGHUP or a reload command.
8. SPIFFE/SPIRE Integration
Vault PKI as a SPIFFE CA
SPIFFE (Secure Production Identity Framework For Everyone) defines a standard for workload identity via X.509 SVIDs — certificates whose URI SAN follows the pattern spiffe://<trust-domain>/<workload-path>.
To issue SPIFFE SVIDs from Vault PKI:
|
|
Issue a SPIFFE SVID:
|
|
Vault PKI vs. Running SPIRE
| Dimension | Vault PKI | SPIRE |
|---|---|---|
| Node attestation | Via Vault auth (AWS IAM, k8s SA, AppRole) | Native SPIRE attestors (TPM, AWS IID, k8s SAT) |
| Workload attestation | Relies on Vault auth binding | Full workload attestation via kernel inspection |
| SVID format | X.509 and JWT (Vault 2.0+ adds JWT-SVID support) | X.509 and JWT SVIDs natively |
| Operational complexity | Lower (Vault already deployed) | Higher (separate control plane) |
| SPIFFE federation | Not native | Full federation between trust domains |
| Scale | Excellent (Vault is battle-tested at scale) | Excellent |
When to use Vault PKI for SPIFFE: You already run Vault, your identity requirements map cleanly to existing Vault auth methods, and you do not need SPIFFE federation between trust domains or the advanced workload attestation that SPIRE provides.
When to use SPIRE: You need multi-cluster federation, hardware-backed node attestation (TPM), or the richer workload registration model. SPIRE can use Vault PKI as its upstream CA — a common hybrid pattern where SPIRE handles attestation and workload registration, but Vault PKI is the certificate authority.
Vault 2.0 added native SPIFFE JWT-SVID support: authenticated workloads can now request JWT-SVIDs from Vault directly, enabling the full SPIFFE identity model without SPIRE for environments that are already Vault-native.
9. The Revocation Story End to End
CRL Configuration Deep Dive
|
|
Delta CRLs (available since Vault 1.12, now stable): Instead of rebuilding the complete CRL on every revocation, delta CRLs are small incremental additions since the last full CRL. For environments with active revocations, this reduces the CRL rebuild from an O(n) operation to O(delta). The delta_rebuild_interval controls how frequently the delta CRL is rebuilt; it defaults to 15 minutes.
Revoking a Certificate
|
|
OCSP Responder
Vault includes a built-in OCSP responder at pki_int/ocsp. Configure it in the URLs:
|
|
The OCSP responder handles DER-encoded single-serial requests per RFC 6960. Limitations: one serial per request; Ed25519-signed certs are not OCSP-checkable (Ed25519 is not supported by the RFC for OCSP signatures).
The “Don’t Build Revocation Infrastructure for Short-Lived Certs” Case
For a concrete example: your service certificates have max_ttl=24h. An attacker steals a private key at T+0. The certificate expires at T+24h. The CA/B Forum considers 7-day certs “short-lived” and exempts them from revocation requirements. At 24 hours, you are well inside that window.
Even if your CRL update interval is 72 hours and your OCSP staple is 12 hours old, the cert expires before you would have caught it via either revocation path. The attacker’s window is bounded by the certificate lifetime, not by your revocation infrastructure’s response time.
For these workloads: set generate_lease=false, no_store=true, consider disable=true in CRL config, and invest the saved operational energy in ensuring your rotation automation is reliable.
10. Cross-Cluster and Cross-Environment PKI
Sharing One Intermediate CA Across Clusters
The simplest multi-cluster pattern is to use the same Vault cluster (or Vault Enterprise performance replication) and the same pki_int mount for all clusters. Each cluster’s cert-manager or Vault Agent authenticates to Vault via its own Kubernetes auth backend (or separate AppRole credentials) and issues certificates from the shared intermediate.
|
|
Separate Intermediates per Environment
For stronger isolation between dev/staging/prod, use separate intermediate CAs — each issued from the same offline root but living in separate Vault mounts (or separate Vault clusters).
|
|
This gives you:
- Different max TTLs per environment (prod can issue up to 90-day certs; dev is limited to 30 days).
- Independent revocation: revoking the staging intermediate does not affect prod.
- Separate audit trails per environment.
- Separate policies: dev teams can have more permissive issuance rights on
pki_int_devwithout touching prod.
Vault Enterprise: Namespace Isolation
Vault Enterprise namespaces provide tenant isolation within a single Vault cluster. Each namespace has its own auth methods, policies, and secrets engine mounts.
|
|
Performance replication in Vault Enterprise: PKI mounts store issued certificates locally per cluster by default (not replicated to secondaries). Roles and configuration are replicated. Enable cross_cluster_revocation=true and unified_crl=true in your CRL config if you need revocations on one cluster to propagate to all clusters — but note this requires a primary cluster to be the coordination point.
11. Monitoring and Operations
Vault PKI Telemetry Metrics
Vault emits the following PKI-specific tidy metrics (via Prometheus or StatsD):
| Metric | Type | Description |
|---|---|---|
secrets.pki.tidy.cert_store_deleted_count |
counter | Expired certs removed from storage |
secrets.pki.tidy.revoked_cert_deleted_count |
counter | Revoked certs cleaned from CRL |
secrets.pki.tidy.success |
counter | Tidy operations completed successfully |
secrets.pki.tidy.failure |
counter | Tidy operations that failed |
secrets.pki.tidy.duration |
summary | Time for tidy operation to complete |
General Vault issuance metrics are captured via the audit log and the vault.core.handle_request latency metrics rather than per-secrets-engine counters.
The vault pki health-check Command
Run this regularly in CI or as a monitoring job:
|
|
Example config file (pki-health-config.json):
|
|
The health check exits with a non-zero code when issues are found, making it directly usable in monitoring pipelines. Key checks:
ca_validity_period: Warn at 12 months remaining on intermediate, critical at 30 days.crl_validity_period: Warn when CRL is 80% through its validity period; critical at 95%. This is the primary CRL expiry alerting mechanism.role_no_store_false: Flags any role withno_store=falsefor review.too_many_certs: Alerts when the cert store grows to performance-impacting levels.
External Certificate Expiry Monitoring
For fleet-wide certificate expiry visibility:
cert-manager Prometheus metrics: cert-manager exposes certmanager_certificate_expiration_timestamp_seconds per Certificate resource. Alert on certificates expiring within your renewal buffer.
|
|
vault-pki-exporter: An open-source Prometheus exporter (github.com/aarnaud/vault-pki-exporter) that queries Vault PKI mounts and exposes:
x509_cert_expiry: Time until expiry for issued certs stored in Vault.x509_crl_expiry: Time until CRL expiry — the primary alerting target for “CRL is about to expire and needs rotation.”
Vault audit log analysis: With no_store=true roles, the audit log is your certificate inventory. Stream audit logs to your SIEM or log platform and build queries against vault.audit.request.path matching pki_int/issue/* to track issuance volume and certificate lifetimes.
Listing Issued Certificates
If no_store=false, you can enumerate issued certificates:
|
|
For no_store=true roles, this list will not include those certificates.
12. Security Hardening
Vault Seal Protection
The intermediate CA private key is encrypted by Vault’s seal. In a properly configured production deployment:
- Auto-unseal with cloud KMS: AWS KMS, Azure Key Vault, or GCP Cloud KMS holds the unseal key. The intermediate CA key is never exposed to a human operator.
- Audit logging: Every certificate issuance (and every Vault operation) is written to the audit log before the response is sent. There is no way to issue a certificate from Vault without an audit record.
- Seal protects at rest: If someone steals the Vault storage backend (Raft snapshots, Consul data), they cannot decrypt the intermediate private key without the unseal key from the KMS.
Vault Policies for PKI Access
The principle of least privilege for PKI:
|
|
Separate policies for operators:
|
|
Rotating the Intermediate CA
When the intermediate CA approaches expiration (or as a proactive security rotation):
|
|
The multi-issuer architecture (Vault 1.11+) means CA rotation does not require a maintenance window. Old and new issuers coexist; roles transition to the new issuer; old certs naturally expire. For 24-hour certs, the entire fleet rotates to the new issuer within one day.
Quick Reference: Full Setup Script
|
|
Common Failure Modes
“certificate signed by unknown authority”: The CA chain was not included in the TLS configuration. Use ca_chain from the Vault response, not just certificate. Distribute the root CA cert to trust stores before deploying.
CRL expiry causing validation failures: If the CRL itself expires (Vault was unreachable and could not rebuild it), clients configured to enforce CRL checking will reject all certificates from that CA. Solution: auto_rebuild=true with auto_rebuild_grace_period, and monitor CRL expiry. Consider disable=true for short-lived cert workloads where you do not need revocation.
cert-manager certificate stuck in False ready state: Check kubectl describe certificate — the events section shows the Vault error. Common causes: policy does not allow the specific role path, Kubernetes auth role’s bound_service_account_namespaces does not include cert-manager’s namespace, or the Vault CA bundle in caBundle does not match what Vault is actually serving.
Storage performance degradation: Too many stored certs from no_store=false roles. Run vault pki health-check pki_int to detect too_many_certs. Enable auto-tidy and run a manual tidy immediately. For the longer term, switch high-volume roles to no_store=true.
Intermediate approaching expiration: The vault pki health-check ca_validity_period check will catch this. Vault does not automatically rotate intermediates — this is an operator action. Build a monitoring alert on intermediate CA expiry with at least a 90-day warning horizon.
Summary
The shift to short-lived certificates is not a performance optimization — it is a fundamental improvement to your security posture. The revocation problem has been structurally unsolved for 30 years; short TTLs solve it with expiry instead. Vault’s PKI secrets engine is the operational foundation for making this work at scale: high-throughput dynamic issuance, authenticated by Vault’s auth methods, constrained by roles, audited by the audit log, and integrated with the Kubernetes certificate lifecycle via cert-manager or the Vault Agent Injector.
The two-tier hierarchy (offline root → Vault intermediate) keeps your most sensitive key material air-gapped while enabling the automated, high-frequency issuance that service mesh and zero-trust networking require. The multi-issuer capability (Vault 1.11+) makes CA rotation a non-event. The vault pki health-check command makes operational hygiene scriptable.
Build the hierarchy once. Automate issuance from day one. Set generate_lease=false. Monitor CRL expiry and CA expiry. Let certificates expire rather than revoking them. That is the production PKI posture.
Sources
- PKI secrets engine — HashiCorp Developer
- PKI secrets engine considerations — HashiCorp Developer
- Quick start: intermediate CA setup — HashiCorp Developer
- Build a CA in Vault with an offline root — HashiCorp Developer
- Build your own CA — HashiCorp Developer
- PKI rotation primitives — HashiCorp Developer
- PKI Unified CRL and OCSP with cross-cluster revocation — HashiCorp Developer
- PKI secrets engine HTTP API — HashiCorp Developer
- vault pki health-check command — HashiCorp Developer
- Vault Agent template stanza — HashiCorp Developer
- Vault Agent Injector examples — HashiCorp Developer
- Configure Vault as cert manager in Kubernetes — HashiCorp Developer
- Vault - cert-manager Documentation
- cert-manager 1.19 release notes
- HashiCorp Vault 2.0 — IBM Identity Federation (InfoQ)
- Certificate Revocation Deep Dive — Axelspire
- Better together: SPIFFE and HashiCorp Vault (Medium)
- vault-pki-exporter — GitHub
- Vault telemetry reference — HashiCorp Developer
- Vault releases — GitHub
Comments