HashiCorp Vault PKI Commands
Enterprise secrets management and PKI with dynamic X.509 certificate generation, automated rotation, and policy-driven issuance.
PKI Engine Setup
Enable PKI secrets engine
Mount the PKI secrets engine at the default path:
vault secrets enable pkiSet maximum TTL for root CA
Tune the PKI engine to allow certificates with up to 10-year validity:
vault secrets tune -max-lease-ttl=87600h pkiEnable intermediate CA engine
Mount a separate PKI engine for the intermediate CA at a custom path:
vault secrets enable -path=pki_int pkiTune intermediate CA TTL
Set the maximum lease TTL for the intermediate CA (5 years):
vault secrets tune -max-lease-ttl=43800h pki_intEnable PKI at custom path
Mount the PKI engine at a custom path for multi-tenant or environment isolation:
vault secrets enable -path=pki_prod pkiRoot CA Management
Generate internal root CA
Generate a self-signed root certificate with the private key stored internally in Vault:
vault write -field=certificate pki/root/generate/internal \
common_name="Example Root CA" \
issuer_name="root-2024" \
ttl=87600hGenerate exported root CA
Generate a root CA and export the private key (use for offline root CA scenarios):
vault write pki/root/generate/exported \
common_name="Example Root CA" \
ttl=87600h \
key_type=rsa \
key_bits=4096Import existing root CA
Import an existing CA certificate and key bundle into Vault:
vault write pki/config/ca [email protected]Configure CA and CRL URLs
Set the issuing certificate URL and CRL distribution points for the CA:
vault write pki/config/urls \
issuing_certificates="https://vault.example.com:8200/v1/pki/ca" \
crl_distribution_points="https://vault.example.com:8200/v1/pki/crl"Read current CA certificate
Retrieve the current CA certificate in PEM format:
vault read -field=certificate pki/cert/caDelete root CA
Remove the root CA and all issued certificates. This operation is irreversible:
vault delete pki/rootIntermediate CA
Generate intermediate CSR
Generate a certificate signing request for the intermediate CA:
vault write -format=json pki_int/intermediate/generate/internal \
common_name="Example Intermediate CA" \
issuer_name="intermediate-2024" \
| jq -r '.data.csr' > pki_int.csrSign intermediate with root CA
Use the root CA to sign the intermediate CSR:
vault write -format=json pki/root/sign-intermediate \
issuer_ref="root-2024" \
csr=@pki_int.csr \
format=pem_bundle \
ttl=43800h \
| jq -r '.data.certificate' > intermediate.cert.pemSet signed intermediate certificate
Import the signed intermediate certificate back into the intermediate CA engine:
vault write pki_int/intermediate/set-signed [email protected]Configure intermediate CA URLs
Set the issuing certificate and CRL URLs for the intermediate CA:
vault write pki_int/config/urls \
issuing_certificates="https://vault.example.com:8200/v1/pki_int/ca" \
crl_distribution_points="https://vault.example.com:8200/v1/pki_int/crl"Cross-sign an intermediate CA
Cross-sign an existing intermediate CA with a different root for trust chain migration:
vault write pki/root/sign-intermediate \
csr=@existing_intermediate.csr \
common_name="Cross-Signed Intermediate CA" \
ttl=43800h \
use_csr_values=trueList issuers
List all configured issuers (root and intermediate CAs) in a PKI mount:
vault list pki/issuersRoles
Create a role for server certificates
Define a role that allows issuing certificates for a specific domain:
vault write pki_int/roles/example-dot-com \
allowed_domains="example.com" \
allow_subdomains=true \
max_ttl=72h \
key_type=rsa \
key_bits=2048Create a role with ECDSA keys
Define a role that issues ECDSA certificates for modern TLS:
vault write pki_int/roles/ecdsa-role \
allowed_domains="example.com" \
allow_subdomains=true \
max_ttl=72h \
key_type=ec \
key_bits=256Create a wildcard certificate role
Allow issuing wildcard certificates for a domain:
vault write pki_int/roles/wildcard-role \
allowed_domains="example.com" \
allow_subdomains=true \
allow_glob_domains=true \
allow_wildcard_certificates=true \
max_ttl=72hCreate a client certificate role
Define a role for mTLS client authentication certificates:
vault write pki_int/roles/client-cert \
allowed_domains="example.com" \
allow_subdomains=true \
client_flag=true \
server_flag=false \
max_ttl=24h \
key_type=ec \
key_bits=256List all roles
View all configured roles for a PKI mount:
vault list pki_int/rolesRead role configuration
View the full configuration of an existing role:
vault read pki_int/roles/example-dot-comDelete a role
Remove a role definition. Existing certificates issued by this role remain valid:
vault delete pki_int/roles/example-dot-comCertificate Issuance
Issue a certificate
Request a new certificate from the intermediate CA using a defined role:
vault write pki_int/issue/example-dot-com \
common_name="app.example.com" \
ttl=24hIssue with Subject Alternative Names
Include additional DNS names in the certificate:
vault write pki_int/issue/example-dot-com \
common_name="app.example.com" \
alt_names="api.example.com,www.example.com" \
ttl=24hIssue with IP SANs
Include IP addresses as subject alternative names:
vault write pki_int/issue/example-dot-com \
common_name="app.example.com" \
ip_sans="10.0.0.1,192.168.1.100" \
ttl=24hIssue with URI SANs
Include URI subject alternative names (common for SPIFFE identities):
vault write pki_int/issue/example-dot-com \
common_name="app.example.com" \
uri_sans="spiffe://example.com/service/app" \
ttl=24hIssue and save to files
Issue a certificate and extract the certificate, key, and CA chain to separate files:
vault write -format=json pki_int/issue/example-dot-com \
common_name="app.example.com" \
ttl=24h \
| tee \
>(jq -r '.data.certificate' > app.crt) \
>(jq -r '.data.private_key' > app.key) \
>(jq -r '.data.ca_chain[]' > ca-chain.crt)Sign an external CSR
Sign a certificate signing request generated outside of Vault:
vault write pki_int/sign/example-dot-com \
[email protected] \
common_name="app.example.com" \
ttl=24hIssue a wildcard certificate
Request a wildcard certificate (requires a role with wildcard permissions):
vault write pki_int/issue/wildcard-role \
common_name="*.example.com" \
ttl=72hCertificate Management
List all certificates
List serial numbers of all certificates issued by the PKI engine:
vault list pki_int/certsRead a specific certificate
Retrieve a certificate by its serial number:
vault read pki_int/cert/3a-cb-16-be-04-e1-60-bb-02-4e-f1-0a-fa-68-e0-76-ff-6e-85-67Revoke a certificate
Revoke a certificate using its serial number:
vault write pki_int/revoke \
serial_number="3a-cb-16-be-04-e1-60-bb-02-4e-f1-0a-fa-68-e0-76-ff-6e-85-67"Tidy certificate storage
Clean up expired certificates and revocation entries from storage:
vault write pki_int/tidy \
tidy_cert_store=true \
tidy_revoked_certs=true \
safety_buffer=72hConfigure auto-tidy
Enable automatic periodic tidying of the certificate store:
vault write pki_int/config/auto-tidy \
enabled=true \
interval_duration=12h \
tidy_cert_store=true \
tidy_revoked_certs=true \
safety_buffer=72hRotate CRL
Force rotation of the certificate revocation list:
vault read -field=success pki_int/crl/rotateCRL & OCSP
Read the current CRL
Retrieve the certificate revocation list in PEM format:
vault read pki_int/cert/crlFetch CRL in PEM format (unauthenticated)
Retrieve the CRL via the unauthenticated HTTP endpoint:
curl -s https://vault.example.com:8200/v1/pki_int/crl/pemConfigure CRL settings
Set CRL expiry, auto-rebuild, and delta CRL options:
vault write pki_int/config/crl \
expiry=72h \
auto_rebuild=true \
auto_rebuild_grace_period=12h \
enable_delta=true \
delta_rebuild_interval=15mEnable OCSP responder (Vault 1.12+)
Configure the built-in OCSP responder for real-time revocation checking:
vault write pki_int/config/urls \
issuing_certificates="https://vault.example.com:8200/v1/pki_int/ca" \
crl_distribution_points="https://vault.example.com:8200/v1/pki_int/crl" \
ocsp_servers="https://vault.example.com:8200/v1/pki_int/ocsp"Query OCSP status
Check the revocation status of a certificate using the Vault OCSP endpoint:
openssl ocsp \
-issuer intermediate-ca.pem \
-cert app.crt \
-url https://vault.example.com:8200/v1/pki_int/ocsp \
-resp_textRead unified CRL and OCSP configuration
View the current CRL configuration including delta CRL and auto-rebuild settings:
vault read pki_int/config/crlACME Support (Vault 1.14+)
Configure cluster path for ACME
Set the cluster path so Vault can construct ACME directory URLs:
vault write pki/config/cluster \
path="https://vault.example.com:8200/v1/pki" \
aia_path="https://vault.example.com:8200/v1/pki"Enable ACME on a PKI mount
Configure the ACME settings and enable the ACME directory:
vault write pki/config/acme \
enabled=trueACME directory URL
The ACME directory is available at the following paths (role-specific or default):
# Default ACME directory (uses default issuer)
https://vault.example.com:8200/v1/pki/acme/directory
# Role-specific ACME directory
https://vault.example.com:8200/v1/pki/roles/example-dot-com/acme/directory
# Issuer-specific ACME directory
https://vault.example.com:8200/v1/pki/issuer/default/acme/directoryUse with certbot
Obtain a certificate from Vault using certbot as the ACME client:
certbot certonly --standalone \
--server https://vault.example.com:8200/v1/pki/acme/directory \
-d app.example.com \
--agree-tos \
--no-eff-emailUse with acme.sh
Obtain a certificate from Vault using acme.sh:
acme.sh --issue --standalone \
-d app.example.com \
--server https://vault.example.com:8200/v1/pki/acme/directoryConfigure external account binding
Require external account binding (EAB) for ACME clients:
vault write pki/config/acme \
enabled=true \
eab_policy=always-requiredRead ACME configuration
View the current ACME settings:
vault read pki/config/acmeCertificate Templates & Policies
ACL policy for certificate issuance
Create a Vault ACL policy that allows issuing certificates from a specific role:
# Save as pki-issue-policy.hcl
path "pki_int/issue/example-dot-com" {
capabilities = ["create", "update"]
}
path "pki_int/sign/example-dot-com" {
capabilities = ["create", "update"]
}
# Allow reading the CA cert and CRL
path "pki_int/cert/ca" {
capabilities = ["read"]
}
path "pki_int/crl" {
capabilities = ["read"]
}Write policy to Vault
Apply the ACL policy to Vault:
vault policy write pki-issue pki-issue-policy.hclRole with restricted SANs
Create a role that restricts which domains and SANs can appear in issued certificates:
vault write pki_int/roles/restricted-role \
allowed_domains="app.example.com,api.example.com" \
allow_bare_domains=true \
allow_subdomains=false \
allow_ip_sans=false \
enforce_hostnames=true \
require_cn=true \
max_ttl=24hRole with allowed URI SANs
Restrict URI SANs to a specific SPIFFE trust domain:
vault write pki_int/roles/spiffe-role \
allowed_uri_sans="spiffe://example.com/*" \
allow_any_name=true \
require_cn=false \
key_type=ec \
key_bits=256 \
max_ttl=1hRead-only PKI policy
Create a policy for operators who only need to view certificates and CRLs:
# Save as pki-read-policy.hcl
path "pki_int/cert/*" {
capabilities = ["read", "list"]
}
path "pki_int/certs" {
capabilities = ["list"]
}
path "pki_int/roles" {
capabilities = ["list"]
}
path "pki_int/roles/*" {
capabilities = ["read"]
}Integration & Automation
Vault Agent auto-renewal template
Configure Vault Agent to automatically issue and renew certificates using a template:
# vault-agent-config.hcl
auto_auth {
method "approle" {
config = {
role_id_file_path = "/etc/vault/role-id"
secret_id_file_path = "/etc/vault/secret-id"
}
}
}
template {
source = "/etc/vault/templates/cert.tpl"
destination = "/etc/ssl/certs/app.crt"
perms = 0644
command = "systemctl reload nginx"
}
template {
source = "/etc/vault/templates/key.tpl"
destination = "/etc/ssl/private/app.key"
perms = 0600
command = "systemctl reload nginx"
}Vault Agent certificate template
Create the Consul Template file that Vault Agent uses to render the certificate:
{{- /* cert.tpl */}}
{{- with pkiCert "pki_int/issue/example-dot-com"
"common_name=app.example.com" "ttl=24h" -}}
{{ .Cert }}
{{ .CA }}
{{- .Key -}}
{{- end -}}Use separate template files for the certificate and key to apply different file permissions.
Kubernetes cert-manager with Vault issuer
Configure cert-manager to use Vault as a certificate issuer in Kubernetes:
# vault-issuer.yaml
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: vault-issuer
namespace: default
spec:
vault:
server: https://vault.example.com:8200
path: pki_int/sign/example-dot-com
auth:
kubernetes:
role: cert-manager
mountPath: /v1/auth/kubernetesRequest a certificate with cert-manager
Create a cert-manager Certificate resource to request a certificate from Vault:
# certificate.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: app-tls
namespace: default
spec:
secretName: app-tls-secret
issuerRef:
name: vault-issuer
kind: Issuer
commonName: app.example.com
dnsNames:
- app.example.com
- api.example.com
duration: 24h
renewBefore: 8hIssue certificate via API with curl
Use the Vault HTTP API directly to issue a certificate:
curl --silent \
--header "X-Vault-Token: $VAULT_TOKEN" \
--request POST \
--data '{
"common_name": "app.example.com",
"alt_names": "api.example.com",
"ttl": "24h"
}' \
https://vault.example.com:8200/v1/pki_int/issue/example-dot-comConsul Connect integration
Configure Consul to use Vault as the Connect CA provider for service mesh certificates:
# consul-server-config.hcl
connect {
ca_provider = "vault"
ca_config {
address = "https://vault.example.com:8200"
token = "s.VAULT_TOKEN"
root_pki_path = "connect-root"
intermediate_pki_path = "connect-intermediate"
}
}Important Notes
Install via: brew install vault (macOS), apt install vault (Debian/Ubuntu), or download from releases.hashicorp.com/vault
In production, use an offline root CA or import an externally managed root. Generate an intermediate CA within Vault for day-to-day issuance. Never issue end-entity certificates directly from the root CA.
Vault excels at issuing short-lived certificates (hours to days). Combine with Vault Agent or cert-manager for automated renewal rather than long-validity certificates.
Vault supports multiple issuers per mount, enabling CA rotation without downtime. Use the issuer_ref parameter to select specific issuers.
For high-volume certificate issuance, consider enabling performance replication or running multiple Vault clusters. Each PKI mount maintains its own CRL.
ACME support (Vault 1.14+) enables Vault to serve as a drop-in replacement for public CAs in internal environments. Use EAB policies to control access.
The PKI secrets engine generates and manages certificates. For signing operations without certificate management, consider the Transit secrets engine instead.
Official docs: developer.hashicorp.com/vault/docs/secrets/pki - comprehensive guides for all PKI operations.