"unable to get local issuer certificate" — Causes & Fix

The client couldn't link the server's certificate to a CA it trusts. Usually the server isn't sending its intermediate certificate; sometimes the client's trust store is the problem.


What you'll seecurl
curl: (60) SSL certificate problem: unable to get local issuer certificate
  1. (60) curl’s code for a certificate it couldn’t verify.
  2. unable to get local issuer certificate The client couldn’t find the certificate that signed this one. Usually the server didn’t send its intermediate.

Most likely fix

  1. Check the chain the server sends with the Certificate Chain Checker.
  2. If an intermediate is missing, serve the full chain (fullchain.pem in Nginx, or the chain file in Apache).
  3. If the chain is complete, update the client’s CA bundle or trust your proxy’s root. Don’t turn verification off.

"unable to get local issuer certificate" means the client checked the server's certificate and couldn't find the certificate that signed it, so it couldn't connect it to a certificate authority it trusts. Most of the time the cause is on the server: it sends its own certificate but not the intermediate between it and the root. Browsers often hide this by filling the gap themselves, which is why the site can look fine in Chrome while curl, Python, Node or Git fail. Less often the client is at fault: an outdated CA bundle, or a corporate proxy re-signing traffic with a root the client doesn't trust.

What It Looks Like

The message comes from OpenSSL, so it reads almost the same everywhere OpenSSL is underneath:

text
# curl curl: (60) SSL certificate problem: unable to get local issuer certificate # Python (requests / urllib) ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1006) # Git fatal: unable to access 'https://git.example.com/repo.git/': SSL certificate problem: unable to get local issuer certificate # Node.js Error: unable to verify the first certificate code: 'UNABLE_TO_VERIFY_LEAF_SIGNATURE'

Node words it differently, but UNABLE_TO_VERIFY_LEAF_SIGNATURE is the same failure: the server's certificate couldn't be linked to a trusted CA.

How a Chain Is Verified

A server certificate is signed by an intermediate certificate, which is signed by a root. Clients trust only the roots in their trust store, so the server must send its own certificate and the intermediates, letting the client walk from one to the next until it reaches a root it has. If a link is missing, the walk stops and the client reports that it can't get the issuer.

text
What the server should send What the client already has --------------------------- --------------------------- 0 www.example.com (leaf) 1 R11 (intermediate) --> signed by ISRG Root X1 (in trust store)

Why It Happens

1. The server sends only its own certificate

The most common cause. The server is configured with the leaf certificate alone, for example Certbot's cert.pem instead of fullchain.pem, or a CA download where the intermediate came in a separate file that was never installed.

2. The wrong or an outdated intermediate

The chain file holds an intermediate that didn't sign this certificate, often left over from an older certificate after the CA changed intermediates at renewal.

3. The client's CA bundle is out of date

The server is fine, but the client's trust store doesn't include the root, typically on old operating systems, minimal Docker images without ca-certificates, or Python on macOS before its certificate installer has been run.

4. A proxy is re-signing the traffic

Corporate firewalls and some antivirus products decrypt HTTPS and re-sign it with their own root. Every HTTPS request from that network fails in tools that don't trust that root.

5. A private or self-signed CA

Internal services signed by a company CA fail on any machine that hasn't had that CA's root installed.

Which side is it? Test from somewhere elseCheck the server from a different network or with the chain checker. If the chain is incomplete there too, fix the server (causes 1–2). If the server's chain is complete and only certain machines fail, fix those clients (causes 3–5).

How to Diagnose It

Ask the server what it sends. -showcerts prints every certificate in the chain; a healthy server sends at least two:

bash
openssl s_client -connect www.example.com:443 -servername www.example.com -showcerts </dev/null # Incomplete chain — only the leaf is sent: # Certificate chain # 0 s:CN = www.example.com # i:C = US, O = Let's Encrypt, CN = R11 # ... # Verify return code: 21 (unable to verify the first certificate) # Complete chain: # 0 s:CN = www.example.com i:CN = R11 # 1 s:CN = R11 i:CN = ISRG Root X1 # Verify return code: 0 (ok)
Check the chain without OpenSSLThe Certificate Chain Checker lists each certificate the server sends and flags a missing or mismatched intermediate.
Check the chain

How to Fix It — Server Side

Fix A — Serve the full chain

Point the server at a file containing your certificate followed by every intermediate, leaf first. With Certbot that file is fullchain.pem:

nginx
# Nginx: the leaf and intermediates in one file ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # Apache 2.4.8+: the same full-chain file SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem # Older Apache: leaf and chain in separate directives SSLCertificateFile /etc/ssl/example.com.crt SSLCertificateChainFile /etc/ssl/example.com.chain.crt

Fix B — Build the chain file by hand when the CA sent separate files

Concatenate your certificate and the intermediate your CA provided, in that order, then reload the server:

bash
cat example.com.crt intermediate.crt > fullchain.pem # Check the intermediate really signed your certificate: openssl verify -untrusted intermediate.crt example.com.crt # example.com.crt: OK sudo nginx -t && sudo systemctl reload nginx

How to Fix It — Client Side

Fix C — Update the CA bundle

bash
# Debian / Ubuntu (and Docker images based on them) sudo apt-get install --reinstall ca-certificates && sudo update-ca-certificates # Alpine apk add --no-cache ca-certificates # Python: refresh the bundle requests uses pip install --upgrade certifi # macOS python.org installs: run "Install Certificates.command" in the Python folder

Fix D — Trust your proxy's or company's root

Add the root certificate to the system store, and to tools that keep their own list. Don't disable verification to work around it.

bash
# System store (Debian / Ubuntu) sudo cp company-root.crt /usr/local/share/ca-certificates/ && sudo update-ca-certificates # Node.js export NODE_EXTRA_CA_CERTS=/path/to/company-root.crt # Git git config --global http.sslCAInfo /path/to/company-root.crt
Don't ship curl -k or verify=FalseSkipping verification makes the error go away by accepting any certificate at all, an attacker's included. Use it only for a one-off test, never in scripts, CI or production code.

Verify the Fix

From a machine that was failing, connect again without any workaround. OpenSSL should end with Verify return code: 0 (ok) and curl should complete the request:

bash
openssl s_client -connect www.example.com:443 -servername www.example.com </dev/null 2>/dev/null \ | grep "Verify return code" # Verify return code: 0 (ok) curl -sS -o /dev/null -w "%{http_code}\n" https://www.example.com/ # 200

Frequently Asked Questions

1. The site works in my browser. Why does curl or my app fail?

Browsers quietly repair an incomplete chain: they cache intermediates from sites you visited earlier and can download a missing one using the address inside the certificate (AIA fetching). curl, OpenSSL, Python, Node, Java and most API clients don’t, so they fail on the same server. A working browser doesn’t prove the chain is complete; the Certificate Chain Checker does.

2. Should I include the root certificate in my chain file?

No need. Clients already have the root in their trust store, and it must come from there to be trusted at all, so sending it only adds bytes to every handshake. Send the leaf certificate followed by every intermediate, in order.

3. Is it safe to use curl -k or verify=False to get past it?

Only for a one-off test against a server you control. Turning verification off accepts any certificate, including an attacker’s, so leaving it in scripts or production code removes the protection TLS exists for. Fix the chain or the client’s CA bundle instead.

4. Why did this start after a renewal when nothing else changed?

The renewal probably installed cert.pem (the leaf only) where fullchain.pem was used before, or the CA moved to a new intermediate and your configuration still points at a chain file containing the old one. Point the server at the full chain the CA issued with this certificate.

5. What is the difference between verify error 20 and error 21?

OpenSSL error 20 (unable to get local issuer certificate) means it couldn’t find the certificate that signed the one it was checking. Error 21 (unable to verify the first certificate) usually follows it and means even the server’s own certificate couldn’t be linked to anything. Together they almost always mean the server sent only its leaf certificate.

6. We’re behind a corporate proxy and every HTTPS call fails this way.

The proxy is decrypting traffic and re-signing it with the company’s own root certificate, which your tools don’t trust. Get that root from IT and add it to the system trust store (and to tools that keep their own, such as Python’s certifi, Node’s NODE_EXTRA_CA_CERTS or the Java keystore).