HashiCorp Vault — Issuing Certificates
Once the CA hierarchy is set up, create roles that define certificate parameters, then issue certificates through those roles.
Create Certificate Roles
Roles define what kind of certificates can be issued:
# Web server role — issues certs for *.example.com
vault write pki_int/roles/web-server \
allowed_domains="example.com" \
allow_subdomains=true \
max_ttl=720h \
key_bits=2048 \
key_type=rsa \
require_cn=true \
allow_ip_sans=true
# Internal services role — shorter TTL, any internal domain
vault write pki_int/roles/internal-service \
allowed_domains="service.internal,svc.cluster.local" \
allow_subdomains=true \
allow_bare_domains=true \
max_ttl=72h \
key_bits=256 \
key_type=ec \
generate_lease=true
Role Parameters
| Parameter | Description |
|---|---|
allowed_domains |
Domains the role can issue certs for |
allow_subdomains |
Allow *.example.com subdomains |
allow_bare_domains |
Allow example.com itself |
max_ttl |
Maximum certificate lifetime |
key_type |
rsa or ec |
key_bits |
Key size (2048/4096 for RSA, 256/384 for EC) |
allow_ip_sans |
Allow IP addresses in SANs |
require_cn |
Require a Common Name |
generate_lease |
Create a Vault lease for the cert |
Issue a Certificate
vault write pki_int/issue/web-server \
common_name="api.example.com" \
alt_names="api-v2.example.com" \
ip_sans="10.0.1.100" \
ttl=720h
Expected output:
Key Value
--- -----
ca_chain [-----BEGIN CERTIFICATE-----...]
certificate -----BEGIN CERTIFICATE-----
MIID...
-----END CERTIFICATE-----
expiration 1706000000
issuing_ca -----BEGIN CERTIFICATE-----...
private_key -----BEGIN RSA PRIVATE KEY-----
MIIE...
-----END RSA PRIVATE KEY-----
private_key_type rsa
serial_number 12:34:56:78:...
Save to Files
# Issue and save cert + key
vault write -format=json pki_int/issue/web-server \
common_name="api.example.com" \
ttl=720h > /tmp/cert.json
# Extract individual files
jq -r '.data.certificate' /tmp/cert.json > /tmp/api.crt
jq -r '.data.private_key' /tmp/cert.json > /tmp/api.key
jq -r '.data.issuing_ca' /tmp/cert.json > /tmp/ca.crt
jq -r '.data.ca_chain[]' /tmp/cert.json > /tmp/ca-chain.crt
# Verify the certificate
openssl x509 -in /tmp/api.crt -text -noout | head -20
Issue via API
curl -s --header "X-Vault-Token: $VAULT_TOKEN" \
--request POST \
--data '{
"common_name": "api.example.com",
"ttl": "720h"
}' \
"$VAULT_ADDR/v1/pki_int/issue/web-server" | jq
Revoke a Certificate
# Revoke by serial number
vault write pki_int/revoke \
serial_number="12:34:56:78:..."
Tidy Expired Certificates
# Clean up expired certificates from storage
vault write pki_int/tidy \
tidy_cert_store=true \
tidy_revoked_certs=true \
safety_buffer=72h
Use with Nginx
# Issue cert for your web server
vault write -format=json pki_int/issue/web-server \
common_name="www.example.com" \
ttl=720h > /tmp/nginx-cert.json
jq -r '.data.certificate' /tmp/nginx-cert.json > /etc/nginx/ssl/cert.pem
jq -r '.data.private_key' /tmp/nginx-cert.json > /etc/nginx/ssl/key.pem
jq -r '.data.ca_chain[]' /tmp/nginx-cert.json > /etc/nginx/ssl/ca-chain.pem
# nginx.conf
server {
listen 443 ssl;
server_name www.example.com;
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
ssl_trusted_certificate /etc/nginx/ssl/ca-chain.pem;
}
Quick Reference
| Command | Description |
|---|---|
vault write pki_int/roles/NAME ... |
Create issuance role |
vault write pki_int/issue/ROLE common_name=CN |
Issue certificate |
vault write pki_int/revoke serial_number=SN |
Revoke certificate |
vault write pki_int/tidy ... |
Clean up storage |
vault list pki_int/certs |
List all certificates |
Next: Certificate Rotation