Vault Error: 'x509: certificate signed by unknown authority' on TLS Connect
Fix Vault's x509 'certificate signed by unknown authority' error: set VAULT_CACERT, install the CA in the system trust store, and serve the full intermediate chain.
- #vault
- #secrets
- #security-hardening
- #troubleshooting
- #errors
Stuck on this HashiCorp Vault error? Get the free incident triage checklist
A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.
Exact Error Message
$ vault status
Error checking seal status: Get "https://vault.example.com:8200/v1/sys/seal-status":
x509: certificate signed by unknown authority
On macOS and some minimal container images the same failure surfaces with a slightly different wrapper:
Error making API request.
URL: GET https://vault.example.com:8200/v1/sys/health
Code: -1. Errors:
* Get "https://vault.example.com:8200/v1/sys/health": tls: failed to verify
certificate: x509: certificate signed by unknown authority
What It Means
The Vault client opened a TCP connection and completed the TLS handshake far enough to receive the server’s certificate, then refused to trust it. Go’s TLS stack — which the vault CLI, the Go SDK, and most Vault-aware agents use — verifies the presented chain against a trust store. If it cannot build a path from the leaf certificate up to a root it already trusts, verification fails with exactly this message. Nothing about the request itself reached Vault’s API; the error is entirely client-side trust evaluation.
There are only two ways this happens. Either the client does not have the issuing CA in its trust store, or the server is not sending the intermediate certificates needed to link its leaf to that CA. The second case is deceptive: a browser may load the same URL fine because browsers cache intermediates from previous sessions and can fetch them via AIA, while Go does neither. So “it works in Chrome” is not evidence that the chain is complete — it usually means the chain is incomplete and something else papered over it.
Common Causes
- Vault’s listener uses a certificate from an internal or self-signed CA that the client machine has never been told to trust.
tls_cert_filecontains only the leaf certificate, with the intermediate CA certificates missing from the bundle.VAULT_CACERTis unset, points at a path that does not exist, or points at the leaf instead of the CA.- The CA was installed into the system trust store but
update-ca-trust/update-ca-certificateswas never run. - A container image (especially
scratch,distroless, oralpinewithoutca-certificates) has no trust store at all. - The listener cert was issued by Vault’s own PKI engine, and that PKI mount’s CA was never distributed to clients.
- A TLS-terminating proxy or corporate MITM appliance sits in front of Vault and presents its own certificate.
Diagnostic Commands
See exactly what the server sends, including whether intermediates are present:
openssl s_client -showcerts -connect vault.example.com:8200 -servername vault.example.com </dev/null
Count the certificates in the output. A correctly configured listener sends the leaf plus every intermediate up to (but not necessarily including) the root. If you see exactly one BEGIN CERTIFICATE block and the issuer is not a public root, your chain is incomplete.
Compare the issuer of the leaf with the subject of what you are trusting:
openssl x509 -in /etc/vault.d/tls/vault.crt -noout -subject -issuer -dates
openssl x509 -in /etc/vault.d/tls/ca.crt -noout -subject -dates
Verify the chain locally with the CA you intend to distribute:
openssl verify -CAfile /etc/vault.d/tls/ca.crt /etc/vault.d/tls/vault.crt
Check what the client environment is actually configured with:
env | grep -E 'VAULT_ADDR|VAULT_CACERT|VAULT_CAPATH|VAULT_CLIENT_CERT'
Inspect the Subject Alternative Names, since a hostname problem produces a different x509 error and needs a different fix:
openssl x509 -in /etc/vault.d/tls/vault.crt -noout -ext subjectAltName
Step-by-Step Resolution
- Obtain the issuing CA certificate. If Vault’s PKI secrets engine issued the listener certificate, pull the CA chain straight from the mount:
curl -s https://vault.example.com:8200/v1/pki/ca/pem -o /etc/vault.d/tls/ca.crt
# or, once you can authenticate:
vault read -field=certificate pki/cert/ca > /etc/vault.d/tls/ca.crt
- Point the client at that CA. For a single file use
VAULT_CACERT; for a directory of PEM files useVAULT_CAPATH:
export VAULT_ADDR=https://vault.example.com:8200
export VAULT_CACERT=/etc/vault.d/tls/ca.crt
vault status
- If many clients need it, install the CA into the system trust store instead of setting an env var everywhere. On Debian/Ubuntu:
sudo cp ca.crt /usr/local/share/ca-certificates/internal-vault-ca.crt
sudo update-ca-certificates
On RHEL, Rocky, or Fedora:
sudo cp ca.crt /etc/pki/ca-trust/source/anchors/internal-vault-ca.crt
sudo update-ca-trust extract
- Fix the server side if
openssl s_client -showcertsreturned only one certificate. Vault’stls_cert_filemust be the leaf plus all intermediates concatenated, leaf first — Vault does not assemble the chain for you:
cat vault-leaf.crt intermediate-ca.crt > /etc/vault.d/tls/vault-fullchain.crt
listener "tcp" {
address = "0.0.0.0:8200"
tls_cert_file = "/etc/vault.d/tls/vault-fullchain.crt"
tls_key_file = "/etc/vault.d/tls/vault.key"
}
Reload Vault to pick up the new bundle — recent versions re-read the certificate files on SIGHUP without dropping the seal state:
sudo systemctl reload vault
openssl s_client -showcerts -connect vault.example.com:8200 </dev/null | grep -c 'BEGIN CERTIFICATE'
- Temporary diagnostic only. If you need to prove that TLS trust — and nothing else — is the blocker, you can bypass verification for a single command. This sends your token over a connection you have not authenticated, so it must never appear in production, in CI, in a Dockerfile, or in any shell profile. Use it once, in an interactive shell, then unset it:
VAULT_SKIP_VERIFY=true vault status # DIAGNOSTIC ONLY - never commit or export this
If this succeeds while the verified call fails, the chain or trust store is confirmed as the sole problem. Go back to steps 1–4 and fix it properly; do not leave the flag in place.
- Confirm the fix from a clean environment, so a leftover variable is not masking anything:
env -u VAULT_SKIP_VERIFY -u VAULT_CACERT vault status
If this passes, the system trust store is correct and every tool on the host — not just the vault CLI — will trust the endpoint.
For generating listener stanzas, PKI role definitions, and chain-assembly steps matched to your CA layout, the Vault prompts in the prompt library can produce a reviewable configuration.
Prevention
- Distribute the internal CA through configuration management or a base image layer so no host is ever missing it.
- Build
tls_cert_filefrom a fullchain artifact in your issuance pipeline, never from the bare leaf. - Add a post-issuance check that runs
openssl s_client -showcertsand asserts the expected certificate count. - Include
ca-certificatesin any container image that talks to Vault, and bake the internal CA into the image. - Ban
VAULT_SKIP_VERIFYin CI by failing the pipeline if the variable is set anywhere in the environment. - Monitor certificate expiry on the listener; a renewal that drops intermediates reproduces this error at the worst moment.
Related Errors
x509: certificate is valid for vault-1.internal, not vault.example.com— a SAN/hostname mismatch, fixed by reissuing with the right names, not by adding a CA.x509: certificate has expired or is not yet valid— the chain is trusted but the validity window is wrong; check clock skew too.tls: bad certificate— usually a client-certificate (mTLS) failure rather than a server-trust failure.connection refusedon/v1/sys/seal-status— nothing is listening, so TLS never starts.
Frequently Asked Questions
Why does curl work but vault status fail? curl on many distributions uses the system trust store while the Vault CLI may be running with a VAULT_CACERT that points somewhere else. Unset VAULT_CACERT and retry to see which store is actually being consulted.
Do I need the root CA or the intermediate on the client? The client needs the trust anchor — normally the root. The server is responsible for sending the intermediates. Trusting an intermediate directly works but breaks the moment that intermediate is rotated.
Is VAULT_SKIP_VERIFY ever acceptable? Only as a throwaway interactive diagnostic to confirm the failure is trust-related, as in step 5. It disables the protection that makes Vault’s transport safe, so any long-lived use — CI, systemd units, container env, shell profiles — is a security defect, not a workaround.
Vault’s PKI engine issues my listener cert — is that a chicken-and-egg problem? Somewhat: you need to reach Vault to get the CA, so fetch it from the unauthenticated /v1/pki/ca/pem endpoint or from your bootstrap artifacts. If authentication itself is failing afterwards, see Vault error: “missing client token” and the wider Vault guides.
Fixed it? Get 500 HashiCorp Vault & DevOps AI prompts — free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.
Did this fix your issue?
Get 500 Battle-Tested DevOps AI Prompts — Free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.