Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

HashiCorp Vault PKI Secrets Engine

By Kokil Thapa | Last reviewed: September 2026

Manual TLS certificate management breaks down fast once you run more than a handful of services. The HashiCorp Vault PKI Secrets Engine solves that by turning Vault into a private certificate authority that issues short-lived certs on demand. I've handled Let's Encrypt and Certbot on Ubuntu servers for years, and I still reach for Vault PKI when internal microservices, staging clusters, or client portals need automated, policy-controlled certificates without exposing a root key. This guide walks through setup, issuance, rotation, and the production patterns that actually hold up.

What is the HashiCorp Vault PKI Secrets Engine and when should you use it?

The PKI secrets engine is one of Vault's built-in engines. It generates X.509 certificates signed by a CA you control inside Vault. Unlike static secrets stored in HashiCorp Vault secrets management, PKI certs are dynamic. Vault creates a fresh key pair, signs the cert, returns both, and can revoke them through CRLs or OCSP.

You reach for Vault PKI when public CAs are wrong for the job. Internal service mesh traffic, database TLS between app servers, mTLS for APIs, and staging environments all fit. Public Let's Encrypt certs work for browser-facing sites. They do not help when service A in a private VPC must trust service B with a cert signed by your own root.

On production Laravel and API deployments I maintain, TLS often terminates at Apache or Nginx with Certbot. Vault PKI sits behind that layer for east-west traffic, webhook callbacks between internal services, and CI pipelines that need ephemeral certs. The two approaches complement each other rather than compete.

Vault PKI Secrets Engine ArchitectureRoot CALong TTL, offlineIntermediate CASigns leaf certs dailyPKI RoleTTL, domains, SANsPKI RoleAPI vs web policyLeaf CertificateLeaf Certificate
HashiCorp Vault PKI Secrets Engine hierarchy: root CA signs intermediate, roles define policy, leaf certs issue on demand

Vault PKI also integrates cleanly with the broader secrets stack. Pair it with External Secrets Operator and Vault on Kubernetes, or with Vault dynamic secrets for databases so both TLS certs and DB credentials rotate from one control plane.

How do you enable and configure the Vault PKI secrets engine?

Start with a running Vault cluster in dev or production mode. The PKI engine mounts at a path like pki/ or pki_int/. Production setups almost always use a two-tier CA: a root with a long TTL kept mostly offline, and an intermediate that signs day-to-day leaf certs.

Step 1: Enable the engine and generate a root CA

Enable PKI at the root path. Generate a self-signed root with a 10-year TTL. Tune max lease TTL so intermediate CAs can be issued beneath it.

vault secrets enable pki
vault secrets tune -max-lease-ttl=87600h pki

vault write pki/root/generate/internal \
  common_name="My Org Root CA" \
  ttl=87600h \
  key_type=rsa \
  key_bits=4096

vault write pki/config/urls \
  issuing_certificates="https://vault.example.com/v1/pki/ca" \
  crl_distribution_points="https://vault.example.com/v1/pki/crl"

The config/urls step matters for clients that fetch the issuing CA or CRL over HTTP. Without it, chain validation fails in browsers and strict TLS libraries.

Step 2: Create an intermediate CA

Mount a second PKI engine for the intermediate. Generate a CSR inside Vault, sign it with the root, then import the signed cert back.

vault secrets enable -path=pki_int pki
vault secrets tune -max-lease-ttl=43800h pki_int

vault write pki_int/intermediate/generate/internal \
  common_name="My Org Intermediate CA" \
  key_type=rsa \
  key_bits=4096

vault write pki/root/sign-intermediate \
  csr=@pki_intermediate.csr \
  format=pem_bundle \
  ttl=43800h

vault write pki_int/intermediate/set-signed certificate=@signed_intermediate.pem

After this, disable or restrict direct use of the root path. All leaf issuance should flow through pki_int/. That limits blast radius if the intermediate key is ever compromised.

Step 3: Define a role with sane defaults

Roles are where policy lives. Set allowed domains, max TTL, key type, and whether clients can request arbitrary SANs.

vault write pki_int/roles/web-server \
  allowed_domains="example.com,*.example.com" \
  allow_subdomains=true \
  max_ttl=720h \
  key_type=rsa \
  key_bits=2048 \
  require_cn=true \
  generate_lease=true

A max_ttl of 720 hours (30 days) is a reasonable starting point for internal services. Public-facing certs from Let's Encrypt cap at 90 days. Shorter TTLs reduce the window if a cert leaks.

Certificate Issuance FlowApp / CIRequests certVault AuthToken / AppRolePKI RoleDomain + TTL checkIntermediate CASigns leaf certCert + KeyReturned to clientDeploy TLSNginx / Apache
HashiCorp Vault PKI Secrets Engine issuance: authenticated clients request certs through roles, intermediate CA signs, app deploys TLS

How do you issue and renew TLS certificates with Vault PKI?

Issuing a cert is a single API call once roles exist. Vault returns the certificate, private key, issuing CA chain, and lease metadata.

Issue a certificate from the CLI

vault write pki_int/issue/web-server \
  common_name="api.example.com" \
  ttl=168h \
  ip_sans="10.0.1.50"

vault write -format=json pki_int/issue/web-server \
  common_name="app.example.com" > cert.json

Parse cert.json in your deploy script. Write certificate, private_key, and issuing_ca to disk. Reload the web server. On Ubuntu with Apache, that means systemctl reload apache2 after dropping files into /etc/ssl/private/.

Automate renewal before expiry

Vault tracks leases when generate_lease=true. Renew with vault lease renew or re-issue before TTL expires. A common pattern I've seen on client projects:

  1. Cron or systemd timer runs every 24 hours.
  2. Script authenticates to Vault with AppRole or a short-lived token.
  3. Script requests a new cert if the current one expires within seven days.
  4. Script writes PEM files and reloads the reverse proxy.
  5. Script logs success or alerts on failure.

Do not store the Vault token in the cron script itself. Pull it from environment variables injected by systemd, or use AppRole with secret_id delivered at boot from cloud metadata. That aligns with CI/CD secrets management best practices and keeps credentials out of Git.

For Kubernetes workloads, cert-manager with a Vault issuer avoids custom cron entirely. The Kubernetes secrets management article covers the broader pattern. Vault PKI becomes one issuer backend among several.

Revoke compromised certificates

Revocation is first-class in Vault PKI. Use serial number or certificate PEM:

vault write pki_int/revoke serial_number=03:ab:cd:...

vault write pki_int/tidy tidy_cert_store=true tidy_revoked_certs=true

Run tidy periodically. Revoked cert metadata accumulates and slows CRL generation on busy clusters.

How does Vault PKI compare to Let's Encrypt and manual OpenSSL?

Teams often ask whether Vault PKI replaces Let's Encrypt. Usually it does not. Each tool fits a different trust boundary.

CriteriaHashiCorp Vault PKILet's Encrypt (Certbot)Manual OpenSSL CA
Best forInternal services, mTLS, private networksPublic HTTPS sites with valid browser trustOne-off certs, legacy scripts
Trust in browsersOnly if you distribute your root CAYes, publicly trustedNo, unless manually installed
AutomationAPI-driven, dynamic TTL, lease trackingACME protocol, 90-day certsManual CSR and sign steps
Policy controlFine-grained roles, audit log, revocationDomain validation onlyWhatever your script enforces
Operational costVault cluster to run and hardenFree certs, minimal infraLow setup, high drift risk
Key storageKeys never leave VaultKeys on server filesystemKeys often on disk or USB

For public law-firm portals and eCommerce sites I ship, Let's Encrypt via Certbot on Apache remains the default for visitor-facing TLS. Vault PKI handles service-to-service calls, internal admin panels, and staging mirrors. A domain registration and hosting setup typically covers the public layer. Vault covers what happens inside the VPC.

Public vs Internal TLS SplitPublic EdgeLet's Encrypt + CertbotNginx / Apache TLSBrowser visitorsTrusted by defaultInternal MeshVault PKI EngineAPI to API mTLSDB + queue TLSPrivate CA requiredBoth
Production split: public HTTPS via Let's Encrypt at the edge, HashiCorp Vault PKI Secrets Engine for internal service TLS

What production pitfalls should you avoid with Vault PKI?

Vault PKI is powerful. It is also easy to misconfigure in ways that only surface at 2 a.m. when certs expire.

Root CA exposure

Never issue leaf certificates directly from the root CA in production. Generate the root, sign one intermediate, then seal or tightly ACL the root mount. If the root key leaks, every cert in your organisation is suspect. Rotating a root means redeploying trust stores everywhere.

Overly permissive roles

A role with allowed_domains="*" and long TTLs defeats the purpose of policy-bound issuance. Scope each role to the service that needs it. A Laravel API role should allow only api.internal.example.com. A wildcard role for staging might allow *.staging.example.com with a 72-hour max TTL.

Missing CRL or OCSP distribution

Clients that validate revocation need reachable CRL or OCSP endpoints. Configure pki/config/urls on both root and intermediate mounts. Test from a host outside the Vault cluster. Firewall rules blocking port 443 to Vault break revocation checks silently.

No monitoring on expiry

Even with auto-renewal, monitor cert expiry externally. Use blackbox probes or OpenTelemetry checks against your endpoints. Vault lease renewal can fail if tokens expire, policies change, or the intermediate hits its own TTL ceiling. I treat cert expiry alerts the same way I treat disk-space alerts on production servers managed through Linux system administration contracts.

Storing issued private keys insecurely

Vault returns the private key to the caller. That is by design. The caller must write it to a restricted path like /etc/ssl/private/ with mode 600 and root ownership. Do not commit PEM files to Git. Use strong passphrases only when your stack requires PKCS#12 bundles. Prefer raw PEM on servers with proper filesystem permissions.

Vault PKI Production ChecklistDeploying Vault PKI?Root offline?Intermediate signsRoles scoped?No wildcard abuseCRL reachable?config/urls setAudit enabledRenewal cronExpiry alertsNever commit private keys to GitUse sealed secrets or ESO in CI
HashiCorp Vault PKI Secrets Engine production checklist: root isolation, scoped roles, CRL URLs, renewal, and no keys in Git

Official references help during audits and onboarding. The HashiCorp Vault PKI secrets engine documentation covers every endpoint. The Vault PKI API reference lists request fields for automation. For baseline X.509 behaviour, the RFC 5280 specification defines profile rules your roles should respect.

How do you integrate Vault PKI with applications and CI pipelines?

Most teams do not issue certs by hand. They wire Vault PKI into deploy pipelines and runtime platforms.

Laravel and PHP applications

A Laravel app behind Nginx does not call Vault directly at runtime in most setups. The deploy step requests a cert, writes PEM paths into the server config, and reloads the proxy. Your Deployer or GitLab CI job authenticates with VAULT_TOKEN or AppRole credentials stored in CI variables. That matches how I deploy sister sites on shared EC2 with GitLab CI and Deployer 7. Secrets stay in Vault and CI, not in the repo.

For outbound mTLS to a payment gateway or internal API, Laravel's HTTP client accepts a cert option. Load PEM content from environment variables injected at deploy time. Never hardcode paths that only exist on one developer laptop.

Kubernetes with cert-manager

Install cert-manager and configure a ClusterIssuer pointing at Vault. Pods receive TLS secrets as Kubernetes secrets. Combine with Sealed Secrets for GitOps for non-PKI credentials. The PKI path handles rotation. Sealed Secrets handles static config.

Multi-cloud and hybrid setups

Vault runs well on a dedicated cluster reachable from all environments. Avoid mounting a separate PKI engine per cloud unless regulatory boundaries require it. A single intermediate CA with role-based separation is easier to audit. See multi-cloud secrets management for the broader identity and policy model.

Client portals with document upload and payment flows, like those built for Mijar Law Associates, need TLS at every layer. Public users hit Let's Encrypt at the edge. Background workers talking to Redis or PostgreSQL over TLS pull certs from Vault PKI on a short TTL.

Key Takeaways

  • Mount the HashiCorp Vault PKI Secrets Engine with a root CA for signing only, and an intermediate CA for daily leaf issuance.
  • Define PKI roles with strict domain lists, short max TTLs, and generate_lease=true so renewals are trackable.
  • Keep Let's Encrypt for public browser trust; use Vault PKI for internal mTLS, service mesh, and staging environments.
  • Automate issuance and renewal through CI, cron, or cert-manager — never store private keys in Git or Ansible plaintext.
  • Configure CRL/OCSP URLs, enable audit logging, and monitor cert expiry independently of Vault lease renewal.
  • Revoke and tidy compromised or expired certificates regularly to keep CRL size manageable.

People Also Ask

What is the difference between Vault PKI and Vault KV secrets?

KV stores static key-value pairs you manage manually. PKI generates dynamic X.509 certificates with key pairs, signed by a CA inside Vault. PKI certs expire on a TTL and can be renewed or revoked. KV secrets persist until you overwrite or delete them.

Can Vault PKI replace Let's Encrypt for public websites?

Not without distributing your root CA to every visitor's browser or operating system. Let's Encrypt chains to publicly trusted roots. Vault PKI chains to a private root. Use Vault PKI for internal traffic and Let's Encrypt for public HTTPS.

How long should TLS certificate TTL be in Vault PKI?

Internal services commonly use 24 hours to 30 days. Shorter TTL limits exposure if a cert leaks. Balance that against renewal frequency and outage risk if automation fails. Start with 168 hours (7 days) and tighten once renewal is proven.

Does Vault store the private key after issuing a certificate?

Vault generates the key pair during issuance and returns the private key to the caller. It does not retain the leaf private key for later retrieval. If you lose the key, request a new certificate. CA private keys remain inside Vault unless exported during setup.

Build a cert lifecycle your team can maintain

The HashiCorp Vault PKI Secrets Engine turns certificate management from a quarterly scramble into an API call with policy, audit, and revocation built in. Start with a two-tier CA, scope your roles tightly, and automate renewal before you point production traffic at it. Pair public Let's Encrypt certs at the edge with Vault PKI inside your network, and you cover both trust models without duplicating effort.

If you need help hardening TLS across Laravel apps, Kubernetes clusters, or enterprise application deployments, the patterns above are the same ones I apply on production systems. Read how to handle secrets in CI/CD pipelines safely for the pipeline side, or Ansible Vault for secrets if you are still migrating off encrypted files. When you want hands-on setup on your infrastructure, contact us and we can map a PKI rollout to your stack.

Frequently Asked Questions

It is a built-in Vault secrets engine that acts as a certificate authority, generating X.509 TLS certificates with fresh key pairs on demand through role-based API calls.

KV stores static key-value pairs you manage manually until you delete or overwrite them. PKI generates dynamic X.509 certificates signed by a CA inside Vault. Each issuance creates a new key pair with a TTL, lease tracking, renewal, and revocation through CRLs or OCSP. They serve different jobs in a secrets stack.

No. Let's Encrypt uses publicly trusted roots. Vault PKI uses a private root. Use Let's Encrypt for public HTTPS and Vault PKI for internal traffic.

Internal services commonly use 24 hours to 30 days. Start around 168 hours once renewal is automated, then tighten. Shorter TTLs limit exposure if a cert leaks.

Vault generates the key pair during issuance and returns the private key to the authenticated caller. It does not retain leaf private keys for later retrieval. If you lose the key, request a new certificate. CA private keys for root and intermediate mounts stay inside Vault under ACL control.

Use it when public CAs are wrong for the trust boundary: internal service mesh traffic, database TLS between app servers, mTLS for APIs, staging environments, webhook callbacks between internal services, and CI pipelines needing ephemeral certs. Let's Encrypt via Certbot on Apache or Nginx remains the default for browser-facing sites. Vault PKI covers east-west traffic inside the VPC where services must trust certs signed by your own root.

Mount PKI at paths like pki/ and pki_int/. Generate a self-signed root with a long TTL such as 87600h, tune max-lease-ttl, and configure issuing_certificates and crl_distribution_points URLs. Create an intermediate CA, sign its CSR with the root, import the signed cert, then restrict the root mount so all leaf issuance flows through the intermediate. That limits blast radius if an intermediate key is compromised.

Roles define issuance policy: allowed domains, max TTL, key type, subdomain rules, and whether arbitrary SANs are allowed. A web-server role might allow example.com and subdomains with max_ttl=720h, require_cn=true, and generate_lease=true for trackable renewals. Scope each role to one service. A role with allowed_domains set to a wildcard and long TTLs defeats the purpose of policy-bound issuance and widens damage if credentials leak.

Issue with a single authenticated API call to pki_int/issue/web-server, passing common_name, ttl, and optional ip_sans. Vault returns the certificate, private key, issuing CA chain, and lease metadata. Write PEM files to restricted paths like /etc/ssl/private/ with mode 600, reload Apache or Nginx, then renew via vault lease renew or re-issue before TTL expires. Without reachable CRL or OCSP URLs configured through pki/config/urls, chain and revocation validation fails in strict TLS libraries.

Run a cron or systemd timer every 24 hours. The script authenticates with AppRole or a short-lived token from environment variables, not hardcoded in Git. It requests a new cert if the current one expires within seven days, writes PEM files, reloads the reverse proxy, and logs or alerts on failure. Do not store the Vault token in the cron script itself. On Kubernetes, cert-manager with a Vault ClusterIssuer avoids custom cron entirely.

Revocation is first-class. Use pki_int/revoke with the serial number or certificate PEM. Run tidy periodically with tidy_cert_store and tidy_revoked_certs so revoked metadata does not accumulate and slow CRL generation on busy clusters. Clients that validate revocation need reachable CRL or OCSP endpoints configured on both root and intermediate mounts. Firewall rules blocking port 443 to Vault break revocation checks silently, so test from a host outside the Vault cluster.

Vault PKI fits internal services, mTLS, and private networks with API-driven dynamic TTL, fine-grained roles, audit logs, and keys that never leave Vault. Let's Encrypt via Certbot suits public HTTPS with browser trust and ACME automation at 90-day caps. Manual OpenSSL works for one-off certs but drifts quickly with keys often sitting on disk. In practice the edge uses Let's Encrypt while east-west traffic, internal admin panels, and staging mirrors use Vault PKI.

Never issue leaf certs directly from the root CA. Avoid overly permissive roles with wildcards and long TTLs. Configure CRL and OCSP URLs and test them from outside the cluster. Monitor cert expiry independently because lease renewal can fail when tokens expire, policies change, or the intermediate hits its own TTL ceiling. Store returned private keys in restricted filesystem paths, not Git or Ansible plaintext. Treat cert expiry alerts the same way you treat disk-space alerts on production servers.

Most Laravel apps behind Nginx do not call Vault at runtime. The deploy step in GitLab CI or Deployer 7 authenticates with VAULT_TOKEN or AppRole stored in CI variables, requests a cert, writes PEM paths into the proxy config, and reloads the server. For outbound mTLS to payment gateways or internal APIs, Laravel's HTTP client accepts a cert option with PEM content from environment variables injected at deploy time. Never hardcode paths that only exist on one developer laptop.

Install cert-manager and configure a ClusterIssuer pointing at Vault. Pods receive TLS secrets as Kubernetes secrets, and cert-manager handles rotation without custom cron scripts. Pair this with External Secrets Operator for broader Vault integration, and Sealed Secrets for static GitOps config while PKI handles dynamic certificate rotation. Vault runs well on a dedicated cluster reachable from all environments. A single intermediate CA with role-based separation is easier to audit than mounting separate PKI engines per cloud unless regulatory boundaries require it.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: