HashiCorp Vault — Issuing Certificates

1 minute read

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