unable to get local issuer certificate

The error "unable to get local issuer certificate" (OpenSSL verify error 20) means the verifier could not find the CA certificate that signed the certificate it is checking. There are only two causes: the server is not sending its intermediate CA certificates, or the root CA is missing from the local trust store. Run `openssl s_client -connect example.com:443 -showcerts` and count the certificates returned — if only one comes back, the server is misconfigured and you must append the intermediates to its certificate file; if the full chain comes back, the missing root is a local trust store problem.

Updated August 19, 2026 Verified against OpenSSL 3.5.7

What the error actually means #

Verification walks a chain. Your server certificate says “I was signed by Example Intermediate CA”, so the verifier looks for a certificate belonging to Example Intermediate CA, then asks who signed that, and continues until it reaches a certificate in the local trust store.

This error is emitted at the moment that walk fails: the verifier holds a certificate, knows the name of its issuer, and cannot find a certificate for that issuer anywhere. It is a missing link, not an invalid signature and not an expired certificate.

There are exactly two places the link can be missing from, and the fix is completely different for each.

Step 1 — find out which cause you have #

Ask the server what it sends
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null

Count the -----BEGIN CERTIFICATE----- blocks in the output:

Just the count
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null 2>/dev/null \
| grep -c "BEGIN CERTIFICATE"
What you see Cause Go to
1 certificate returned, and the CA is a public one The server is not sending its intermediates Cause A
2 or more returned, still fails The root is not in your local trust store Cause B
Verify return code: 0 (ok) Chain is fine; your application is using a different trust store Cause B

Cause A — the server is not sending its intermediates #

A TLS server is required to send its own certificate and every intermediate needed to reach a trusted root. Serving only the end-entity certificate is the single most common cause of this error.

The fix is to concatenate the certificates into one file, leaf first, then each intermediate in order, walking up toward the root:

Build a correctly ordered chain file
cat certificate.crt intermediate.crt > fullchain.crt

Then point the server at the combined file:

nginx has no separate chain directive — ssl_certificate must point at the concatenated file.

ssl_certificate     /etc/ssl/certs/fullchain.crt;
ssl_certificate_key /etc/ssl/private/private.key;

Apache 2.4.8 and later also read the chain from the certificate file.

SSLCertificateFile    /etc/ssl/certs/fullchain.crt
SSLCertificateKeyFile /etc/ssl/private/private.key

HAProxy expects the key appended to the same PEM file as the chain.

cat certificate.crt intermediate.crt private.key > /etc/haproxy/certs/site.pem

Reload the service, then confirm the chain is complete:

openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
| grep "Verify return code"
Expected output
Verify return code: 0 (ok)

Where do I get the intermediate? #

Your CA publishes it, and the certificate itself tells you where. Read the CA Issuers URL out of the Authority Information Access extension:

openssl x509 -in certificate.crt -noout -text \
| grep -A1 "Authority Information Access"
Expected output
Authority Information Access:
  OCSP - URI:http://ocsp.example-ca.com
  CA Issuers - URI:http://crt.example-ca.com/intermediate.der

That file is usually DER-encoded, so convert it to PEM before concatenating it.

Cause B — the root is not in your trust store #

If the server returns a complete chain and verification still fails, the root CA at the top of that chain is not trusted by whatever is doing the verifying. This is normal for a private or corporate CA, and for containers built FROM scratch that ship no CA bundle at all.

You can confirm it by verifying against the root explicitly. If this succeeds, the certificate is fine and only trust is missing:

openssl verify -CAfile root-ca.crt -untrusted intermediate.crt certificate.crt
Expected output
certificate.crt: OK

Then install the root:

sudo cp root-ca.crt /usr/local/share/ca-certificates/root-ca.crt
sudo update-ca-certificates

The file must have a .crt extension and contain PEM, or update-ca-certificates silently skips it.

sudo cp root-ca.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust extract
sudo cp root-ca.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates

A bare alpine image has no trust store at all until you apk add ca-certificates.

sudo security add-trusted-cert -d -r trustRoot \
-k /Library/Keychains/System.keychain root-ca.crt

Runtimes that ignore the system trust store #

Several runtimes ship their own CA bundle and will keep failing after you have fixed the system store. Point each at the right bundle:

Runtime How to add the root
Node.js NODE_EXTRA_CA_CERTS=/path/to/root-ca.crt
Python requests REQUESTS_CA_BUNDLE=/path/to/ca-bundle.crt
Python ssl / urllib SSL_CERT_FILE=/path/to/ca-bundle.crt
Git git config --global http.sslCAInfo /path/to/ca-bundle.crt
Java keytool -importcert -trustcacerts -alias corp-root -file root-ca.crt -keystore $JAVA_HOME/lib/security/cacerts
curl curl --cacert /path/to/root-ca.crt

What not to do #

Frequently asked questions

What does "verify error num=20" mean?
It is the numeric form of this same error. OpenSSL returns error code 20, X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, when it cannot locate the issuer of a certificate it is trying to verify.
Why does the site work in my browser but fail with curl?
Browsers quietly repair incomplete chains. They cache intermediates from previous visits and follow the Authority Information Access extension to fetch a missing intermediate over HTTP. OpenSSL and curl do neither, so a chain that is genuinely broken only fails outside the browser. The server is still misconfigured.
Is -k or verify=False a valid fix?
No. Both disable certificate verification entirely, which removes the protection TLS exists to provide and makes the connection trivially interceptable. Use them only to confirm a diagnosis, never in anything that ships.
How do I fix this in Python requests?
The underlying cause is the same. Point requests at a CA bundle that contains the missing issuer with requests.get(url, verify='/path/to/ca-bundle.crt'), or install certifi and make sure the corporate root is appended to its bundle.

References