Building a PKI hierarchy using OpenBao
Public Key Infrastructure (PKI) is the backbone of secure communications. At the heart of any PKI are Certificate Authorities (CAs): long-lived root CAs and shorter-lived intermediate CAs that issue the day-to-day certificates we actually use.
In this post, we’ll walk through setting up a simple PKI hierarchy using OpenBao (a community fork of HashiCorp Vault). We’ll create a 50-year root CA, generate a 10-year intermediate CA signed by that root, configure certificate URLs for proper validation and issue short-lived leaf certificates for services. This is a great way to get a realistic PKI setup for testing or lab environments.
Why use a hierarchy?
Best practice is to never issue certificates directly from your root CA. The root should live as long as possible, ideally offline, and only sign intermediates. Intermediates can then be rotated every few years while keeping the root intact. Leaf certificates, in turn, should be short-lived (days or months).
This gives you:
Longevity: Root CA is stable, rarely touched
Flexibility: Intermediates can be renewed or revoked
Security: Compromised intermediates don’t affect the root
Step 1. Create the Root CA
We’ll start by enabling a PKI engine for the root and tuning its lifetime to 50 years:
bao secrets enable -path=pki_root pki
bao secrets tune -max-lease-ttl=438000h pki_root
Now generate a self-signed root certificate:
bao write pki_root/root/generate/internal \
common_name="Area536 Root CA" \
ttl=438000h
You’ll see a warning about missing AIA/CRL URLs. We’ll fix that shortly.
Step 2. Create the Intermediate CA
Enable a new PKI engine for the intermediate, tuned for a 10-year lifetime:
bao secrets enable -path=pki_int pki
bao secrets tune -max-lease-ttl=87600h pki_int
Generate a CSR for the intermediate:
bao write -format=json pki_int/intermediate/generate/internal \
common_name="Area536 Intermediate CA" \
| jq -r '.data.csr' > pki_intermediate.csr
Have the root sign the CSR:
bao write -format=json pki_root/root/sign-intermediate \
csr=@pki_intermediate.csr \
format=pem_bundle ttl=87600h \
| jq -r '.data.certificate' > pki_intermediate_cert.pem
Finally, import the signed certificate back into the intermediate:
bao write pki_int/intermediate/set-signed certificate=@pki_intermediate_cert.pem
Now your intermediate CA is fully trusted by the root.
Step 3. Configure Certificate URLs
To make certificate validation work smoothly, configure the issuing and revocation endpoints:
bao write pki_int/config/urls \
issuing_certificates="http://127.0.0.1:8200/v1/pki_int/ca" \
crl_distribution_points="http://127.0.0.1:8200/v1/pki_int/crl"
(Do the same for pki_root if you want the root’s CRL/AIA published.)
Step 4. Define Roles
Roles define what certificates the intermediate can issue. For example, to issue certs for example.com:
bao write pki_int/roles/web-server \
allowed_domains="example.com" \
allow_subdomains=true \
max_ttl="720h"
This role allows issuing certificates for example.com and any subdomain, with a max lifetime of 30 days.
Step 5. Issue a Leaf Certificate
Finally, request a certificate for a service:
bao write pki_int/issue/web-server common_name="app.example.com"
This returns a PEM bundle including the private key, signed certificate and the CA chain.
Putting It All Together
With this setup you now have:
Root CA — 50-year lifetime, only signs intermediates
Intermediate CA — 10-year lifetime, issues leaves
Leaf certificates — short-lived (e.g. 30 days), safe to rotate automatically
This hierarchy mimics how production PKI is deployed, giving you realistic and secure testing in your OpenBao environment.