SSL: CERTIFICATE_VERIFY_FAILED - unable to get local issuer certificate, even though cert is valid
ID: security/tls-certificate-chain-ordering
Version Compatibility
| Version | Status | Introduced | Deprecated | Notes |
|---|---|---|---|---|
| any | active | — | — | — |
Root Cause
TLS requires the full certificate chain: leaf cert -> intermediate(s) -> root CA. Browsers often cache intermediates and work anyway, but programmatic clients (curl, Python requests, Node.js) fail. The server must send intermediates; the client only trusts root CAs.
genericWorkarounds
-
95% success Include full chain in order: leaf, intermediate(s), but NOT root
cat server.crt intermediate.crt > fullchain.pem # leaf first, then intermediate(s)
-
92% success Test with openssl s_client or SSL Labs, not just a browser
openssl s_client -connect example.com:443 -showcerts # verify full chain is sent
-
88% success Use ACME/certbot which automatically includes the correct chain
certbot uses fullchain.pem by default which includes leaf + intermediates in correct order
Dead Ends
Common approaches that don't work:
-
Install leaf certificate only, assuming clients have intermediate certs
92% fail
Browsers cache intermediate certs from previous visits. curl, Python, and Node.js don't cache intermediates. They fail on every connection.
-
Add root CA cert to the server's chain file
72% fail
Root CA should NOT be in the chain. Clients already have root CAs in their trust store. Including it wastes bandwidth and can confuse some TLS implementations.
-
Test TLS with a browser and assume all clients will work
88% fail
Browsers have intermediate certificate caching (AIA fetching). Programmatic clients don't. Browser testing misses missing intermediate cert issues.