Vault PKI Secrets Engine: Building Your Own Certificate Authority

TLS certificates are everywhere - service-to-service communication, API endpoints, internal tools, developer portals.
And if you've ever managed them manually, you know the pain: tracking expiration dates in spreadsheets, rotating certificates at 2 AM because someone forgot to renew, securing private keys across teams, and maintaining CA infrastructure that nobody fully understands.
Vault's PKI secrets engine changes this game entirely. It turns Vault into a certificate authority where applications request certificates on-demand, Vault issues them with appropriate lifetimes, and rotation becomes automatic. No more calendar reminders for certificate expiration. No more shared private keys. No more "who has access to the CA?"
Let me walk you through building a complete internal PKI infrastructure with Vault.
_____
Why Vault for PKI?
Before we dive into the how, let's talk about the why:
- Dynamic certificates - Generate certificates on-demand with short lifetimes. If someone compromises a certificate, it expires in hours anyway. The blast radius shrinks dramatically.
- Automated rotation - Applications request new certificates before expiration. No human intervention required. No missed renewals.
- Centralized control - One place to manage all internal certificates, with complete audit logging for every issuance. When compliance asks "who issued that certificate?", you have the answer.
- API-driven - Integrate certificate issuance into deployment pipelines, service mesh configurations, and Kubernetes workloads. Certificates become just another API call.
- No external dependencies - Your CA infrastructure runs on infrastructure you control. No third-party CA outages affecting your internal services.
_____
PKI Architecture
A proper PKI has multiple tiers, and understanding this structure is crucial before you start building:

Here's why this structure matters:
- Root CA - Keep this offline or in an HSM. It only signs intermediate CA certificates, maybe once every few years. If this gets compromised, everything is compromised. Treat it like the nuclear launch codes.
- Intermediate CA - This runs in Vault and handles day-to-day certificate issuance. If it gets compromised, you revoke it and create a new one. Painful, but recoverable.
- End-entity certificates - These are what your services actually use. Short-lived by design. Compromise one, and it expires before the attacker can do much damage.
______
Setting Up the PKI
Step 1: Enable PKI Secrets Engine for Root CA
# Enable PKI engine for root CA (keep separate from intermediate)
vault secrets enable -path=pki_root pki
# Configure max TTL (10 years for root)
vault secrets tune -max-lease-ttl=87600h pki_root__
Step 2: Generate Root CA
For production, you'd generate this offline and import it. For learning purposes, we'll generate it directly in Vault:
1# Generate root certificate
2vault write -format=json pki_root/root/generate/internal \
3 common_name="Organization Root CA" \
4 issuer_name="root-2024" \
5 ttl=87600h \
6 key_bits=4096 \
7 organization="Your Organization" \
8 ou="Security" \
9 country="IN" \
10 > root_ca.json
11
12# Extract the certificate
13cat root_ca.json | jq -r '.data.certificate' > root_ca.crt
14
15# Configure CA URLs
16vault write pki_root/config/urls \
17 issuing_certificates="https://vault.example.com:8200/v1/pki_root/ca" \
18 crl_distribution_points="https://vault.example.com:8200/v1/pki_root/crl"__
Step 3: Enable Intermediate CA
# Enable PKI for intermediate CA
vault secrets enable -path=pki_intermediate pki
# Configure max TTL (3 years for intermediate)
vault secrets tune -max-lease-ttl=26280h pki_intermediate__
Step 4: Generate and Sign Intermediate CA
This is where it gets interesting. The intermediate CA generates a CSR, and the root CA signs it:
1# Generate CSR for intermediate
2vault write -format=json pki_intermediate/intermediate/generate/internal \
3 common_name="Organization Intermediate CA" \
4 issuer_name="intermediate-2024" \
5 key_bits=4096 \
6 organization="Your Organization" \
7 ou="Security" \
8 country="IN" \
9 > intermediate_csr.json
10
11# Extract CSR
12cat intermediate_csr.json | jq -r '.data.csr' > intermediate.csr
13
14# Sign with root CA
15vault write -format=json pki_root/root/sign-intermediate \
16 csr=@intermediate.csr \
17 format=pem_bundle \
18 ttl=26280h \
19 > intermediate_signed.json
20
21# Extract signed certificate
22cat intermediate_signed.json | jq -r '.data.certificate' > intermediate_ca.crt
23
24# Import signed certificate back to intermediate
25vault write pki_intermediate/intermediate/set-signed \
26 certificate=@intermediate_ca.crt__
Step 5: Configure Intermediate CA URLs
vault write pki_intermediate/config/urls \
issuing_certificates="https://vault.example.com:8200/v1/pki_intermediate/ca" \
crl_distribution_points="https://vault.example.com:8200/v1/pki_intermediate/crl"______
Creating Roles
Roles are certificate templates. They define what domains are allowed, TTLs, key usage, and other constraints. Think of them as policies for certificate issuance.
Web Server Role
1vault write pki_intermediate/roles/web-servers \
2 allowed_domains="example.com,internal.example.com" \
3 allow_subdomains=true \
4 allow_bare_domains=false \
5 max_ttl=720h \
6 ttl=72h \
7 key_bits=2048 \
8 key_type=rsa \
9 key_usage="DigitalSignature,KeyEncipherment" \
10 ext_key_usage="ServerAuth" \
11 require_cn=true \
12 organization="Your Organization" \
13 country="IN"Service-to-Service (mTLS) Role
For internal services that need mutual TLS, you want shorter TTLs and both server and client auth:
1vault write pki_intermediate/roles/service-mesh \
2 allowed_domains="service.consul,svc.cluster.local" \
3 allow_subdomains=true \
4 allow_localhost=true \
5 max_ttl=24h \
6 ttl=1h \
7 key_bits=256 \
8 key_type=ec \
9 key_usage="DigitalSignature,KeyEncipherment" \
10 ext_key_usage="ServerAuth,ClientAuth" \
11 require_cn=false \
12 allow_ip_sans=true \
13 enforce_hostnames=falseNotice the 1-hour TTL. For service mesh traffic, certificates rotate constantly. This is a feature, not a bug.
Client Certificate Role
1vault write pki_intermediate/roles/client-certs \
2 allowed_domains="users.example.com" \
3 allow_subdomains=true \
4 max_ttl=8760h \
5 ttl=720h \
6 key_bits=2048 \
7 key_type=rsa \
8 key_usage="DigitalSignature" \
9 ext_key_usage="ClientAuth" \
10 no_store=true______
Issuing Certificates
Via CLI
1# Issue certificate for web server
2vault write -format=json pki_intermediate/issue/web-servers \
3 common_name="api.example.com" \
4 alt_names="api.internal.example.com" \
5 ttl=72h \
6 > api_cert.json
7
8# Extract components
9cat api_cert.json | jq -r '.data.certificate' > api.crt
10cat api_cert.json | jq -r '.data.private_key' > api.key
11cat api_cert.json | jq -r '.data.ca_chain[]' > ca_chain.crtVia API
1curl -s \
2 --header "X-Vault-Token: ${VAULT_TOKEN}" \
3 --request POST \
4 --data '{
5 "common_name": "api.example.com",
6 "alt_names": "api.internal.example.com",
7 "ttl": "72h"
8 }' \
9 ${VAULT_ADDR}/v1/pki_intermediate/issue/web-serversVia Terraform
This is particularly powerful for infrastructure automation:
1resource "vault_pki_secret_backend_cert" "api" {
2 backend = vault_mount.pki_intermediate.path
3 name = vault_pki_secret_backend_role.web_servers.name
4 common_name = "api.example.com"
5 alt_names = ["api.internal.example.com"]
6 ttl = "72h"
7
8 auto_renew = true
9 min_seconds_remaining = 3600 # Renew when 1 hour left
10}
11
12output "certificate" {
13 value = vault_pki_secret_backend_cert.api.certificate
14 sensitive = true
15}
16
17output "private_key" {
18 value = vault_pki_secret_backend_cert.api.private_key
19 sensitive = true
20}_____
Automated Certificate Management
Vault Agent for Auto-Renewal
Vault Agent can automatically fetch and renew certificates, then trigger application reloads:
1# vault-agent-config.hcl
2vault {
3 address = "https://vault.example.com:8200"
4}
5
6auto_auth {
7 method "kubernetes" {
8 mount_path = "auth/kubernetes"
9 config = {
10 role = "web-app"
11 }
12 }
13
14 sink "file" {
15 config = {
16 path = "/vault/token"
17 }
18 }
19}
20
21template {
22 source = "/vault/templates/cert.tpl"
23 destination = "/vault/secrets/tls.crt"
24 perms = 0644
25 command = "nginx -s reload"
26}
27
28template {
29 source = "/vault/templates/key.tpl"
30 destination = "/vault/secrets/tls.key"
31 perms = 0600
32 command = "nginx -s reload"
33}
34
35The certificate template would look like:
36
37{{ with secret "pki_intermediate/issue/web-servers" "common_name=api.example.com" "ttl=24h" }}
38{{ .Data.certificate }}
39{{ .Data.ca_chain }}
40{{ end }}When the certificate approaches expiration, Vault Agent automatically fetches a new one and reloads nginx. Zero human intervention.
__
Kubernetes cert-manager Integration
If you're on Kubernetes, cert-manager integrates beautifully with Vault:
1# vault-issuer.yaml
2apiVersion: cert-manager.io/v1
3kind: ClusterIssuer
4metadata:
5 name: vault-issuer
6spec:
7 vault:
8 path: pki_intermediate/sign/web-servers
9 server: https://vault.example.com:8200
10 auth:
11 kubernetes:
12 role: cert-manager
13 mountPath: /v1/auth/kubernetes
14 serviceAccountRef:
15 name: cert-manager
16
17# certificate.yaml
18apiVersion: cert-manager.io/v1
19kind: Certificate
20metadata:
21 name: api-tls
22 namespace: production
23spec:
24 secretName: api-tls-secret
25 duration: 72h
26 renewBefore: 24h
27 subject:
28 organizations:
29 - Your Organization
30 commonName: api.example.com
31 dnsNames:
32 - api.example.com
33 - api.internal.example.com
34 issuerRef:
35 name: vault-issuer
36 kind: ClusterIssuer______
Certificate Revocation
Sometimes you need to revoke certificates before they expire.
Revoke a Certificate
vault write pki_intermediate/revoke \
serial_number="39:dd:2e:90:b7:23:1f:8d:d3:7d:31:c5:1b:da:84:d0:5b:65:31:58"Tidy Operations
Clean up expired certificates and keep your CRL manageable:
1# Manual tidy
2vault write pki_intermediate/tidy \
3 tidy_cert_store=true \
4 tidy_revoked_certs=true \
5 tidy_revoked_cert_issuer_associations=true \
6 safety_buffer=72h
7
8# Enable auto-tidy
9vault write pki_intermediate/config/auto-tidy \
10 enabled=true \
11 interval_duration=12h \
12 tidy_cert_store=true \
13 tidy_revoked_certs=true \
14 safety_buffer=72h______
Best Practices
After running PKI infrastructure in production, here's what I've learned:
- Keep root CA offline - After signing the intermediate, disable or heavily restrict root CA access. Better yet, generate root offline and import only the certificate.
- Short-lived end-entity certificates - Hours or days, not months. This makes revocation less critical because compromised certificates expire quickly anyway.
- Rotate intermediate periodically - Plan for intermediate CA rotation every 1-2 years. Overlap validity periods to avoid disruption during the transition.
- Separate roles by use case - Don't create one role that does everything. Web servers, service mesh, and client certificates have different requirements.
- Monitor expiration - Alert well before CA certificates expire. Intermediate CA expiration breaks everything downstream.
- Test revocation - Verify your applications actually check CRL/OCSP. Many don't by default, which defeats the purpose.
_____
Wrapping Up
Vault PKI transforms certificate management from an operational burden into an automated service. Applications request certificates when they need them, get short-lived credentials, and rotate automatically. No more spreadsheets. No more 3 AM renewals.
The initial setup - root CA, intermediate CA, roles, policies - takes effort. But once running, you eliminate an entire category of incidents: expired certificates, compromised keys, and manual rotation failures.
Start with a non-production environment. Issue certificates to test services. Verify revocation works. Then roll out to production with confidence that your internal PKI is built on solid foundations.
