Vault
PKI external CA secrets engine
Enterprise
Appropriate Vault Enterprise license required
Overview
The PKI External CA secrets engine is a HashiCorp Enterprise plugin that enables Vault to acquire signed leaf certificates from external Certificate Authorities (CAs) through the ACME (Automatic Certificate Management Environment) protocol. You can use the plugin to leverage public CAs like Let's Encrypt, or other ACME-compliant CAs while maintaining the security of your Vault server.
You can use the PKI external CA plugin to:
- Manage ACME accounts with external CAs.
- Automate certificate acquisition through the ACME protocol.
- Cache and distribute certificates to applications.
Setup
Enable the plugin:
$ vault secrets enable \ -path=pki-external-ca \ vault-plugin-secrets-pki-external-caConfigure an ACME account:
$ vault write pki-external-ca/config/acme-account/letsencrypt-prod \ directory_url="https://acme-v02.api.letsencrypt.org/directory" \ email_contacts="admin@example.com,security@example.com" \ key_type="ec-256"Create a role definition file (
role.json) to set the domains available for request and how Vault should issue certificates. The examples below show common configurations:{ "acme_account_name": "letsencrypt-prod", "allowed_domains": "example.com,www.example.com,api.example.com", "allowed_domain_options": "bare_domains" }
- Use the JSON file to create the roles in Vault:
$ vault write pki-external-ca/role/web-servers @role.json
Certificate workflows
The PKI External CA secrets engine supports two distinct workflows for requesting certificates, similar to the sign and issue endpoints in the standard PKI secrets engine.
You can choose the workflow that best fits your security requirements and operational needs.
Workflow comparison
| Feature | CSR Workflow | Identifier Workflow |
|---|---|---|
| Complexity | Higher - client must generate CSR and associated key | Lower - client only provides identifiers |
| Key transmission | Never leaves client | Vault transmits to client after generation |
| Certificate caching | Not supported | Vault caches both certificate and private key |
When to use each workflow
Use the CSR workflow when:
- You have existing key management processes and infrastructure.
- You need to comply with security policies that prohibit key transmission.
- You deploy single-instance applications where key sharing is not needed.
Use the identifier workflow when:
- You prioritize operational simplicity with minimal client complexity.
- You want Vault to handle key generation and management.
- You need to retrieve both the certificate and key at a later time.
How to request certificates
To create a new order and retrieve the public certificate, and optionally the private key, the following steps must be performed.
In the identifier workflow, you provide domain names. Vault generates and caches the private key with the certificate so you can retrieve the key later if needed.
Create a new order and save the
order_idreturned by Vault:$ vault write pki-external-ca/role/web-servers/new-order \ identifiers="www.example.com,api.example.com"Check the order status, waiting for the order to be ready for challenge fulfillment (status
awaiting_challenge_fulfillment) Note that in certain situations, such as when identifiers have been pre-authorized, the order may skip directly tocompletedwithout needing challenge fulfillment.$ vault read pki-external-ca/role/web-servers/order/01936d8e-7c3a-7890-b123-456789abcdef/statusFetch the challenge token and auth key for each identifier from Vault:
$ vault read pki-external-ca/role/web-servers/order/01936d8e-7c3a-7890-b123-456789abcdef/challenges \ identifier="www.example.com" \ challenge_type="http-01"Fulfill challenges (place HTTP token or DNS records) as ACME server expects.
Notify Vault that challenges are ready:
$ vault write pki-external-ca/role/web-servers/order/01936d8e-7c3a-7890-b123-456789abcdef/fulfilled-challenge \ identifier="www.example.com" \ challenge_type="http-01"Wait for certificate issuance to be completed (status
completed)$ vault read pki-external-ca/role/web-servers/order/01936d8e-7c3a-7890-b123-456789abcdef/statusRetrieve certificate when ready
$ vault read pki-external-ca/role/web-servers/order/01936d8e-7c3a-7890-b123-456789abcdef/fetch-cert
Role configuration options
Within each role, you can specify which ACME account to use, the allowed domains, and how to fulfill challenges.
Limit allowed domains
Roles support several options for controlling the domains clients can request.
Each option influences how Vault matches the allowed_domains value against
identifiers in the new-order request.
Bare domains
Use the bare_domains option to only allow matching domains without a subdomain
prefix. For example, the following configuration allows example.com and
www.example.com, but denies api.example.com and sub.example.com:
$ vault write pki-external-ca/role/example \
acme_account_name="letsencrypt-prod" \
allowed_domains="example.com,www.example.com" \
allowed_domain_options="bare_domains"
Subdomains
Use the subdomains option to explicitly match to subdomains of a given domain
and domains with the www prefix. For example, the following configuration
allows www.example.com, api.example.com and sub.example.com, but denies
example.com:
$ vault write pki-external-ca/role/example \
acme_account_name="letsencrypt-prod" \
allowed_domains="example.com" \
allowed_domain_options="subdomains"
Wildcards
Use the wildcards option to match to any single subdomain of a given domain
and domains with the www prefix provided it succeeds a DNS-01 challenge.
For example, the following configuration allows www.example.com and any
subdomain of the form <subdomain>.example.com, but denies example.com and
any grandchild domains (*.*.example.com):
$ vault write pki-external-ca/role/example \
acme_account_name="letsencrypt-prod" \
allowed_domains="*.example.com" \
allowed_domain_options="wildcards" \
allowed_challenge_types="dns-01"
Globs
Use the globs option to match against glob patterns for the given domain. For
example, the following configuration allows api.prod.example.com and
web.staging.example.com, but denies example.com or prod.example.com:
$ vault write pki-external-ca/role/example \
acme_account_name="letsencrypt-prod" \
allowed_domains="*.prod.example.com,*.staging.example.com" \
allowed_domain_options="globs"
Identity templating
Use ACL path templating in the allowed_domains field to create dynamic domain
allowlists based on the authenticated entity. For example, the following
configuration allows <username>.users.example.com provided the entity name of
the user and the requested subdomain match:
$ vault write pki-external-ca/role/user-certs \
acme_account_name="letsencrypt-prod" \
allowed_domains="{{identity.entity.name}}.users.example.com" \
allowed_domain_options="bare_domains"
When user "alice" requests a certificate:
- Allowed:
alice.users.example.com - Denied:
bob.users.example.com
Available template variables:
{{identity.entity.id}}- Entity ID{{identity.entity.name}}- Entity name{{identity.entity.metadata.<key>}}- Entity metadata{{identity.groups.names.<group>}}- Group membership
Domain matching behavior
The following table breaks down how the allowed_domains and
allowed_domain_options for a role interact to forbid or allow identifiers when
you call the new-order endpoint.
| allowed_domains | allowed_domain_options | Allowed identifiers | Forbidden identifiers | Notes |
|---|---|---|---|---|
| *.example.com | None | All | ||
| foo.example.com | foo.example.com | |||
| *.example.com | bare_domains | None | All | |
| example.com | bare_domains | example.com | ||
| foo.example.com | bare_domains | foo.example.com | example.com | bare_domains does nothing and Vault forbids example.com because it is not in allowed_domains |
| *.example.com | wildcards | *.example.com | foo.example.com | |
| example.com | bare_domains,subdomains | example.com, foo.example.com | ||
| example.com | subdomains | foo.example.com | example.com | |
| *.example.com | globs | foo.example.com, baz.foo.example.com | example.com, *.example.com | |
| *.example.com | globs,wildcards | foo.example.com, baz.foo.example.com, .example.com, .foo.example.com | example.com | |
| xn--rksmrgs-5wao1o.se | bare_domains | räksmörgås.se, xn--rksmrgs-5wao1o.se | ||
| räksmörgås.se | bare_domains | räksmörgås.se, xn--rksmrgs-5wao1o.se |
Monitoring and troubleshooting
Check order status
$ vault read pki-external-ca/role/web-servers/order/01936d8e-7c3a-7890-b123-456789abcdef/status
Order statuses:
new- Order created, not yet submitted to ACME serversubmitted- Submitted to ACME serverawaiting_challenge_fulfillment- Waiting for client to fulfill challengesnotify_acme_server_challenges_completed- Ready to notify ACME serverprocessing_challenge- ACME server is validating challengesfetching_certificate- Retrieving issued certificatecompleted- Certificate issued successfullyexpired- Order expired before completionrevoked- Vault revoked the certificateerror- An error occurred
List active orders
$ vault list pki-external-ca/role/web-servers/active-orders
Lookup certificate by serial
$ vault read pki-external-ca/lookup/cert/03:e7:1f:a2:3d
View ACME account details
$ vault read pki-external-ca/config/acme-account/letsencrypt-prod
Known limitations
Load-balanced applications with multiple instances
The current implementation works best when a single orchestrator or instance manages certificate requests. If you deploy multiple instances of an application behind a load balancer, where each instance independently requests certificates from Vault, you may encounter challenges:
- Multiple instances may create separate orders for the same identifiers simultaneously.
- Each order generates unique challenge tokens specific to that order.
- The ACME CA may validate challenges against a different instance than the one that received the challenge information and cause validation failures or certificate issuance problems.
Recommended workarounds:
Single orchestrator pattern: Designate one instance or orchestrator to handle certificate requests. After Vault issues the certificate, distribute it to all instances that need the certificate.
Identifier workflow with caching: Use the identifier workflow (not CSR workflow) so Vault generates and caches both the certificate and private key. Once cached, clients can fetch the the same certificate and key from Vault.
IP address identifiers
The plugin currently does not support requesting certificates with IP address identifiers.
ACME Renewal Information (ARI)
The plugin does not currently support the ACME Renewal Information (ARI) extension (RFC 9773), which allows ACME servers to notify clients when they should renew certificates. As a result, your clients need to decide certificate renewal timing independently.
Automatic ACME account key rotation
The plugin cannot automatically rotate ACME account keys based on key age. We
recommend manually rotating account keys as needed using the
/config/acme-account/:name/rotate-key endpoint.
Vault Agent integration
Use Vault Agent to automate the full certificate lifecycle for PKI external CA mounts. Refer to Automate certificates with Vault Agent and PKI external CA for configuration details.
API documentation
For detailed API documentation, see PKI External CA API.