In this episode we'll cover the PKI Secrets Engine — making Vault a Certificate Authority that issues short-lived TLS certificates automatically. We'll set up a Root CA and Intermediate CA, issue certificates on-demand, and understand CRL, OCSP, and revocation.

After covering the Transit Secrets Engine in episode 6 to secure data at rest, this episode secures the next layer: communication between services. The problem is simple but real: almost every modern application uses TLS/SSL, but who takes care of the certificates? The classic answers are "sometimes," "when we remember," or "when the browser already shows an error."
Why does this topic matter in the real world? Expired internal TLS certificates are one of the most common and most embarrassing incidents in companies. Certificates for api.internal.local or microservice connections are often created manually with OpenSSL, installed once, then forgotten — until the staging environment suddenly errors with "certificate expired" in the middle of the night. Not to mention the bad practice of reusing the same certificate across many servers, or self-signed certificates that make other teams distrustful.
The PKI Secrets Engine solves this problem in a very Vault way: certificates become short-lived (e.g. a 24-hour TTL), issued automatically via API, and rotation becomes part of the normal lifecycle — not a yearly event done by hand. Let's dissect how Vault becomes a real Certificate Authority (CA).
The PKI Secrets Engine enables Vault to act as a Certificate Authority: creating X.509 keys and certificates, issuing certificates on-demand, and managing their revocation.
The recommended production architecture uses a two-tier CA:
| Tier | Function | Characteristics |
|---|---|---|
| Root CA | The root of trust for the whole system | Offline/rarely used, stored very securely, long TTL (10 years) |
| Intermediate CA | The CA that actually issues certificates | Medium TTL, easy to rotate, if leaked it can simply be revoked from the Root CA |
Why two tiers? The principle of compartmentalization. If there's only one Root CA and its key leaks, the entire infrastructure must be rebuilt. With an intermediate CA, if the intermediate key leaks, we just revoke it and create a new intermediate — the safe Root CA is untouched. This mirrors the real-world PKI structure (for example, Let's Encrypt uses Root + Intermediate).
Important
The key PKI philosophy in Vault: short-TTL certificates make revocation almost unnecessary. If a certificate only lives 24 hours and the application automatically requests a new one, then the "leaked certificate" problem becomes a small issue that simply waits for expiration — not an emergency incident. This is the opposite of the traditional 1-year certificate pattern that must be manually revoked.
First, we mount the PKI secrets engine for the Root CA:
vault secrets enable -path=pki pkiOutput:
Success! Enabled the pki secrets engine at: pki/Cap the maximum TTL for the Root CA — 10 years:
vault secrets tune -max-lease-ttl=87600h pkiGenerate a self-signed Root CA:
vault write pki/root/generate/internal \
common_name="devvnull.local Root CA" \
ttl=87600hOutput:
Key Value
--- -----
certificate -----BEGIN CERTIFICATE-----
MIIFxzCCA6+gAwIBAgIUDhH... (certificate PEM here)
-----END CERTIFICATE-----
expiration 2036-08-02T09:15:00.000Z
issuing_ca -----BEGIN CERTIFICATE-----
MIIFxzCCA6+gAwIBAgIUDhH... (same as certificate for self-signed root)
-----END CERTIFICATE-----
serial_number 4a:7c:...Warning
Store the Root CA certificate in a safe place, and note well that the Root CA private key never leaves Vault — the output above only shows the public certificate. In production, consider placing the Root CA on a separate, rarely accessed Vault cluster, or even keeping it offline.
Configure the URLs for the CRL (Certificate Revocation List) and OCSP:
vault write pki/config/urls \
issuing_certificates="http://vault.local:8200/v1/pki/ca" \
crl_distribution_points="http://vault.local:8200/v1/pki/crl"Now mount a second PKI for the intermediate CA:
vault secrets enable -path=pki_int pkivault secrets tune -max-lease-ttl=43800h pki_intGenerate a Certificate Signing Request (CSR) on the intermediate mount:
vault write -format=json pki_int/intermediate/generate/internal \
common_name="devvnull.local Intermediate CA" \
ttl=43800hOutput (CSR in JSON format, because it's long):
{
"data": {
"csr": "-----BEGIN CERTIFICATE REQUEST-----\nMIICzzCCAbc...\n-----END CERTIFICATE REQUEST-----",
"key_id": "b0a2c46d...",
"private_key_type": "rsa"
}
}This CSR is the intermediate's "request" to the Root. To sign it, save the CSR to a file and sign it with the Root CA mount:
vault write pki/root/sign-intermediate \
csr=@/tmp/intermediate.csr \
format=pem_bundle \
ttl=43800hOutput:
Key Value
--- -----
certificate -----BEGIN CERTIFICATE----- (intermediate certificate signed by Root)
...
ca_chain -----BEGIN CERTIFICATE----- (Root + Intermediate chain)
...
expiration 2034-08-02T09:20:00.000Z
issuing_ca -----BEGIN CERTIFICATE----- (Root CA)Finally, set the signed certificate as the intermediate CA on the pki_int mount:
vault write pki_int/intermediate/set-signed certificate=@/tmp/intermediate.crtOutput:
Success! Data written to: pki_int/intermediate/set-signedBefore we can issue certificates, we must define a role — the rules about what kind of certificates may be issued (which domains, what TTL):
vault write pki_int/roles/my-role \
allowed_domains="internal.local" \
allow_subdomains=true \
max_ttl="720h" \
default_ttl="24h" \
key_type="rsa" \
key_bits="2048"Output:
Success! Data written to: pki_int/roles/my-roleWarning
The most common mistake when starting with PKI: forgetting to create a role. The command vault write pki_int/issue/my-role will fail if the my-role role doesn't exist, or if the requested common_name doesn't match allowed_domains. Remember the rules: role = issuance policy, and every issuance must go through a role. Read a no matching role error as a cue to check the role name and allowed_domains.
This is the core moment of this episode — issuing a certificate with a single command:
vault write pki_int/issue/my-role common_name="api.internal.local" ttl="24h"Output:
Key Value
--- -----
certificate -----BEGIN CERTIFICATE-----
MIIE... (certificate PEM for api.internal.local)
-----END CERTIFICATE-----
ca_chain [-----BEGIN CERTIFICATE----- (intermediate) -----END CERTIFICATE-----
-----BEGIN CERTIFICATE----- (root) -----END CERTIFICATE-----]
expiration 2026-08-03T09:30:00.000Z
issuing_ca -----BEGIN CERTIFICATE-----(intermediate)-----END CERTIFICATE-----
private_key -----BEGIN RSA PRIVATE KEY-----
MIIE... (private key matching this certificate)
-----END RSA PRIVATE KEY-----
private_key_type rsa
serial_number 6c:1f:9a:...Caution
Notice the output above: private_key is emitted by Vault only once, at issuance time. This means Vault doesn't store the certificate's private key — the application requesting the certificate must store that private key securely (for example, in a file with strict permissions, or via Vault Agent templates). Once this response is gone, the private key can't be retrieved again; the certificate itself must be re-issued.
The resulting certificate has a 24-hour lifetime (expiration: 2026-08-03). This means a PKI-aware application will request a new certificate every day — and automatically get a fresh key + certificate. This pattern makes the "forgotten expired certificate" attack impossible.
| Aspect | Traditional Certificate (1 year) | Short-Lived Certificate (24 hours) |
|---|---|---|
| Lifetime | 1 year or more | Hours / days |
| Rotation | Manual, often forgotten | Automatic, part of the app lifecycle |
| Impact of key leak | Long, requires manual revocation | Short, just wait for expiration |
| Issuance process | Fill out a CSR form, wait for manual CA | One API call, in seconds |
| Visibility | Scattered, hard to track | Centralized, audited in Vault |
| Behavior when the CA has issues | Unaffected | App can't get a new cert (needs retry) |
Even though short-lived certificates reduce the need for revocation, Vault still provides standard PKI mechanisms:
/v1/pki/crl endpoint (and /v1/pki_int/crl for the intermediate).Revoking a certificate that's already problematic:
vault write pki_int/revoke serial_number="6c:1f:9a:..."Output:
Key Value
--- -----
revocation_time 1722570000
revocation_time_rfc3339 2026-08-02T10:00:00.000Z
state revokedAfter the revoke, that serial number goes into the CRL, and connections verifying via CRL/OCSP will reject the certificate.
Tip
You can also view all certificates issued by Vault along with their status: vault list pki_int/certs for the list of serial numbers, and vault read pki_int/cert/<serial> for the details. This is a kind of automatic "certificate inventory" that never existed in the manual management era.
One of the most tangible benefits of the PKI engine is total visibility. Let's see which certificates have been issued:
vault list pki_int/certsOutput:
Keys
----
2a:1f:9c:...
6c:1f:9a:...
a3:8e:2b:...Each serial number represents one certificate. You can pull its details:
vault read pki_int/cert/6c:1f:9a:...Output:
Key Value
--- -----
certificate -----BEGIN CERTIFICATE-----
MIIE... (certificate PEM)
-----END CERTIFICATE-----
revocation_time <nil>
revocation_time_rfc3339 <nil>
state issuedNotice the state: issued and revocation_time: <nil> columns — signs that this certificate is still active. With an inventory like this, teams no longer need a manual spreadsheet to track "who has which certificate."
But visibility alone isn't enough — the hardest part is automatic renewal. A 24-hour TTL certificate is useless if requesting it is still manual. In production, these patterns are used:
| Renewal Pattern | How It Works | Best for |
|---|---|---|
| Vault Agent templates (episodes 14-15) | An Agent beside the app refreshes the cert + key into files, then reloads the service | Apps that read certs from files |
| Vault SDK in the application | The app requests a new certificate before the TTL ends via a library | Apps that want full control |
| Vault Secrets Operator (episode 17) | A Kubernetes operator refreshes certs and restarts pods automatically | Kubernetes workloads |
The key to success is one mindset: don't wait for the certificate to expire — schedule renewal well before the TTL ends (for example, at 2/3 of the certificate's lifetime). That way, "expired certificate" changes from an incident to something that never happens.
| Mistake | Symptom | Solution |
|---|---|---|
| Forgetting to create a role | no matching role error when issuing | Create a role with the right allowed_domains |
common_name outside allowed_domains | Permission/rejected error | Make sure the domain matches the role (and allow_subdomains if needed) |
Requested TTL exceeds max_ttl | TTL gets capped or rejected | Check vault secrets tune -max-lease-ttl and the role's max_ttl |
| Only using one mount (no intermediate) | Root CA risk of leaking / hard to rotate | Follow the Root + Intermediate architecture |
| Forgetting to save the private key at issuance | Certificate unusable without the key | Store private_key securely immediately after issuance |
Not using allow_subdomains for wildcard subdomains | Issuing app.internal.local is rejected | Set allow_subdomains=true on the role |
| Certificates applied to services without auto-renewal | Expiry problem returns | Build automatic rotation (Vault Agent / SDK) — episodes 14-16 |
In episode 7, we've covered the PKI Secrets Engine: why expired internal certificates are a real problem, the recommended Root CA + Intermediate CA architecture, the full flow of creating the Root, the intermediate CSR signed by the Root, role creation, and issuing short-lived certificates on-demand. We also looked at CRL, OCSP, revocation mechanics, and the common traps.
The essence of this episode: short-lived certificates turn certificate rotation from a tedious annual event into an automatic daily lifecycle. With a 24-hour TTL, there's no more such thing as a "forgotten expired certificate" — and if a leak happens, the impact is bounded by the certificate's lifetime, not by how fast humans respond.
In episode 8, we'll round out the secrets engines with three additional engines frequently used in the field: TOTP (centralized 2FA), SSH (OTP & CA signing), and AWS (dynamic IAM credentials). Three different security problems, one shared mindset: secrets are not stored, but managed. Keep your enthusiasm up!