Turning OpenBao into an internal Root CA and Intermediate CA, issuing TLS certificates on-demand with short TTLs, and understanding the role of policies in automating certificate issuance for internal services that communicate over TLS.

In episode 5 you used the Transit Secrets Engine to encrypt and decrypt sensitive data without storing the data itself in OpenBao. Episode 6 moves one step further: OpenBao does not only guard keys, it also issues certificates for you. With the PKI Secrets Engine, OpenBao can act as an internal Certificate Authority (CA) that prints TLS certificates automatically, whenever needed, with very short validity periods. This is the foundation of zero-trust architecture and authenticated service-to-service communication.
Public Key Infrastructure (PKI) is the system that governs the issuance, distribution, and revocation of digital certificates. In the modern web ecosystem, TLS certificates are usually issued by public CAs such as Let's Encrypt. But inside an internal network, you often don't want every service to use a public certificate — you need an internal CA trusted only by machines you own.
OpenBao can play two roles in a PKI hierarchy:
This separation matters: if an intermediate is compromised, you simply revoke that intermediate's certificate and build a new one — the Root CA stays safe. This is why OpenBao can automatically act as both a Root CA and an Intermediate CA in a single installation.
Like other engines, PKI is enabled via bao secrets enable. Its default mount path is pki. You also need to raise the TTL limit, because CA certificates live for a very long time:
bao secrets enable pki
bao secrets tune -max-lease-ttl=87600h pki
bao write pki/root/generate/internal \
common_name="OpenBao Root CA" \
ttl=87600hbao secrets enable pki enables the engine at the pki path, while bao write pki/root/generate/internal creates the root key pair and its certificate in a single step. The -max-lease-ttl=87600h option is equivalent to 10 years — a common duration for a root CA.
Issuing all certificates directly from the root is possible, but not good practice. The best practice is to use an intermediate. Here is a comparison of the two:
| Aspect | Root CA (pki) | Intermediate CA (pki_int) |
|---|---|---|
| Key source | Self-generated | CSR signed by the Root CA |
| Usage | Almost never direct | Issues service certificates |
| Issuance path | pki/root/generate/internal | pki_int/issue/<role> |
| Compromise risk | Critical, hard to revoke | Can be revoked separately |
| Typical TTL | Years | Hours to days |
The intermediate creation flow begins by enabling a second mount:
bao secrets enable -path=pki_int pki
bao secrets tune -max-lease-ttl=43800h pki_int
bao write pki_int/intermediate/generate/internal \
common_name="OpenBao Intermediate CA" | tee /tmp/intermediate.csr.jsonThe generated CSR is then signed by the root, and the resulting certificate is imported back into pki_int. After that, pki_int officially becomes an intermediate ready to issue certificates for internal services.
To be able to issue certificates, a PKI mount needs a role. A role defines the allowed domains, maximum TTL, and other rules that govern the shape of certificates that may be born. Example role for an internal domain:
bao write pki_int/roles/my-role \
allowed_domains="internal.local" \
allow_subdomains=true \
max_ttl="24h"Once the role exists, issuing a certificate is as easy as one command — no waiting for the manual process of a traditional CA:
bao write pki_int/issue/my-role \
common_name="api.internal.local" \
ttl="24h"bao write pki_int/issue/my-role common_name="api.internal.local" issues a certificate for api.internal.local valid for 24 hours. The certificate is born instantly over the API, complete with its private key.
The returned output looks roughly like this:
{
"data": {
"certificate": "-----BEGIN CERTIFICATE----- ...",
"private_key": "-----BEGIN PRIVATE KEY----- ...",
"issuing_ca": "-----BEGIN CERTIFICATE----- ...",
"serial_number": "4f:2b:9d:...",
"expiration": 1753824000
}
}You just save private_key and certificate and install them on your service. Because the TTL is short, the service is expected to renew its own certificate before expiry — a pattern known as certificate rotation.
Short-TTL certificates are a pillar of zero-trust:
Tip
For internal certificates used only between services, start with a 24-hour TTL, then gradually lower it to 1 to 6 hours once automatic rotation runs smoothly. Remember, max_ttl on the role is the upper limit — the TTL at issuance must not exceed that limit.
All issued certificates can be tracked from the pki_int mount:
bao list pki_int/certs
bao read pki_int/cert/4f:2b:9d:...bao list pki_int/certs shows the serial numbers of all active certificates, and bao read pki_int/cert/<serial> shows the details of each. This is useful for auditing — making sure no foreign certificate was issued without the team's knowledge.
In this episode 6 you turned OpenBao into an internal CA: understood the roles of the Root CA and Intermediate CA, enabled the pki and pki_int engines, built the CA hierarchy, created issuance roles, and issued on-demand TLS certificates with short TTLs for api.internal.local and other internal services.
Key takeaways:
allowed_domains, max_ttl, and allow_subdomains are controlled here.pki_int/certs — use it for issuance auditing.In the next episode, episode 7, we pause from secrets engines and discuss Access Control Policies — how OpenBao decides who may access which path, in HCL format, complete with capabilities and privilege enforcement testing.