Vault
Automate certificates with Vault Agent and PKI external CA
Enterprise
Appropriate Vault Enterprise license required
Vault Agent can act as an ACME client for public certificate authorities, automating the full certificate lifecycle without manual operator intervention. When Agent issues or renews a certificate, it automatically re-renders any templates that reference it.
Requirements
- Vault Enterprise v2.0.0 or later
- A Vault PKI external CA secrets engine mount configured for ACME workflows. Refer to PKI external CA secrets engine for setup instructions.
- The agent's auth method must grant
updateaccess to the PKI mount'sacme/*paths and the targetroles/<role>/acme/paths. - For HTTP-01 challenges: either a writable filesystem path served by an existing web server, or a free TCP port for Agent to bind a temporary listener on.
Configuration
Add one or more pki_external_ca stanzas to your Agent configuration file.
Each block must have a unique label that you reference in templates.
Minimal example (HTTP-01 with shared challenge path)
The following example uses an existing web server to serve the ACME HTTP-01 challenge files:
vault {
address = "https://vault.example.com:8200"
}
auto_auth {
method "kubernetes" {
config {
role = "my-app"
}
}
}
pki_external_ca "web-tls" {
mount_path = "pki"
role = "web-server"
challenge_type = "http-01"
identifiers {
dns = ["app.example.com"]
}
http_01 {
challenge_path = "/var/www/html/.well-known/acme-challenge"
}
destination {
path = "/etc/certs"
pem_bundle = false
}
}
template {
destination = "/etc/nginx/tls/app.crt"
contents = <<-EOT
{{ with pkiCertExternalCa "web-tls" -}}
{{ .Certificate }}
{{- end }}
EOT
}
Full example (explicit CSR with RSA key)
The following example uses an explicit CSR with a 2048-bit RSA key. Agent binds
a temporary HTTP listener on port 8402 to serve the ACME challenge:
pki_external_ca "internal-api" {
mount_path = "pki-internal"
role = "api-server"
namespace = "team-a"
challenge_type = "http-01"
percent_renew_before_expiry = 30
csr {
CN = "api.internal.example.com"
C = "US"
ST = "California"
L = "San Francisco"
O = "Example Corp"
OU = "Engineering"
SANs = ["api.internal.example.com", "api-v2.internal.example.com"]
private_key {
rsa {
bits = 2048
}
}
}
http_01 {
listener_addr = "0.0.0.0:8402"
}
destination {
path = "/etc/pki/api"
pem_bundle = true
filename_prefix = "bundle"
umask = "077"
}
}
pki_external_ca stanza reference
| Parameter | Required | Default | Description |
|---|---|---|---|
<label> | no | — | A unique name for this block, used as the argument to pkiCertExternalCa in templates. |
mount_path | yes | — | PKI secrets engine mount path (e.g., pki). |
role | yes | — | PKI role used to issue the certificate. |
namespace | no | Inherited from vault.namespace | Vault namespace override for this block. |
challenge_type | no | "http-01" | ACME challenge type. Only http-01 is currently supported. |
percent_renew_before_expiry | no | 20 | Percentage of TTL remaining at which renewal is triggered. Must be between 1 and 99. |
identifiers | no¹ | — | Auto-generated CSR identifiers block. Mutually exclusive with csr. |
csr | no¹ | — | Explicit CSR configuration block. Mutually exclusive with identifiers. |
http_01 | yes when challenge_type = "http-01" | — | HTTP-01 challenge configuration block. |
destination | yes | — | Output path configuration for the issued certificate and key. |
¹ Exactly one of identifiers or csr is required.
identifiers block
| Parameter | Required | Description |
|---|---|---|
dns | yes | List of DNS names to include in the auto-generated CSR. At least one entry is required. |
csr block
| Parameter | Required | Description |
|---|---|---|
CN | yes | Common name for the certificate subject. |
private_key | yes | Key configuration sub-block. |
C | no | Country field for the certificate subject. |
ST | no | State field for the certificate subject. |
L | no | Locality field for the certificate subject. |
O | no | Organization field for the certificate subject. |
OU | no | Organizational unit field for the certificate subject. |
SANs | no | Subject alternative names. Each entry must be non-empty. |
csr.private_key block
Exactly one of rsa or ecdsa is required.
rsa sub-block
| Parameter | Required | Description |
|---|---|---|
bits | yes | RSA key size in bits. Minimum value: 2048. |
ecdsa sub-block
| Parameter | Required | Description |
|---|---|---|
type | yes | ECDSA curve. Supported values: p256, p384, p521. |
http_01 block
Exactly one of challenge_path or listener_addr is required.
| Parameter | Required | Description |
|---|---|---|
challenge_path | no | Filesystem path served by an existing web server for challenge files (e.g., /var/www/html/.well-known/acme-challenge). |
listener_addr | no | host:port for Agent to bind a temporary HTTP listener for challenge responses. The host and port must both be present, and the port must be between 1 and 65535. |
destination block
| Parameter | Required | Default | Description |
|---|---|---|---|
path | yes | — | An existing directory where Agent writes certificate files. Duplicate paths across pki_external_ca blocks are not allowed. |
pem_bundle | yes | — | When true, writes a single <filename_prefix>.pem bundle. When false, writes separate <filename_prefix>.crt and <filename_prefix>.key files. |
filename_prefix | no | "cert" | Base filename for output files. Must not contain path separators (/, \) or file extensions (.pem, .crt, .key). |
umask | no | "077" | Octal umask applied when writing certificate files. |
Template function: pkiCertExternalCa
Use the pkiCertExternalCa template function to access certificate data for a
named pki_external_ca block. The function accepts exactly one argument: the
block label.
{{ pkiCertExternalCa "" }}
The return value is an object whose fields match the Vault PKI issue response,
including .Certificate, .PrivateKey, and .IssuingCA. If the certificate
has not been issued yet, the function returns nil. Use a {{ with }} block to
guard against this during agent startup:
template {
destination = "/etc/app/tls.crt"
contents = <<-EOT
{{ with pkiCertExternalCa "web-tls" -}}
{{ .Certificate }}
{{ .IssuingCA }}
{{- end }}
EOT
}
template {
destination = "/etc/app/tls.key"
contents = <<-EOT
{{ with pkiCertExternalCa "web-tls" -}}
{{ .PrivateKey }}
{{- end }}
EOT
}
Automatic template re-render
When any pki_external_ca block issues or renews a certificate, Agent restarts
its template rendering runner. All templates that call pkiCertExternalCa
re-render automatically — no polling or manual restart is required.
Considerations
- Block labels must be unique across all
pki_external_castanzas in a single agent configuration file. - Destination
pathvalues must be unique and must point to existing directories. Agent does not create them. - The default
umaskof077means only the agent process owner can read the written key files. percent_renew_before_expiryis evaluated against the remaining TTL of the certificate. A value of20triggers renewal when 20% of the certificate's lifetime remains.