SSL certificate problem: unable to get local issuer certificate means your client could not build a chain of trust from the server’s certificate to a certificate authority it trusts. The temptation is to disable verification. Do not โ that removes protection against interception entirely. The real fix is usually straightforward.
๐ Table of Contents
What the Error Means
When a client connects over TLS, the server presents its certificate plus any intermediate certificates. The client must link that chain to a trusted root in its local CA store. The error appears when a link is missing โ either the server did not send the intermediate, or your local trust store is missing or outdated.
Diagnose which by inspecting the chain.
openssl s_client -connect example.com:443 -showcerts </dev/null
# Look for the verification result at the end:
# Verify return code: 0 (ok)
# Verify return code: 20 (unable to get local issuer certificate)
# Verify return code: 21 (unable to verify the first certificate)
Return code 21 generally means the server is at fault โ it did not send its intermediate certificate. Return code 20 generally means your local trust store is the problem.
Cause 1: The Server Is Not Sending Intermediates
A common server misconfiguration. Browsers often hide it because they cache intermediates from previous visits, so the site “works in Chrome” while curl and your CI pipeline fail.
# Count the certificates the server sends
openssl s_client -connect example.com:443 -showcerts </dev/null 2>/dev/null \
| grep -c "BEGIN CERTIFICATE"
# 1 means only the leaf โ the intermediate is missing
If you control the server, fix it there. nginx needs the leaf and intermediates concatenated into one file, in that order.
# Correct order: leaf first, then intermediates
cat leaf.crt intermediate.crt > fullchain.crt
server {
# Use the full chain, not just the leaf certificate
ssl_certificate /etc/ssl/certs/fullchain.crt;
ssl_certificate_key /etc/ssl/private/privkey.key;
}
Certbot’s fullchain.pem already contains the chain โ pointing nginx at cert.pem instead is the single most frequent cause of this error on Let’s Encrypt sites.
Cause 2: An Outdated or Missing CA Bundle
If the server’s chain is complete, your local trust store is stale.
# Debian / Ubuntu
sudo apt update && sudo apt install --reinstall ca-certificates
sudo update-ca-certificates
# RHEL / Fedora
sudo dnf reinstall ca-certificates
sudo update-ca-trust
# Alpine (very common in Docker images)
apk add --no-cache ca-certificates
update-ca-certificates
# macOS
brew install ca-certificates
# or update curl, which bundles its own
brew install curl
Minimal Docker images are a frequent source of this error because they ship without any CA bundle at all. Adding ca-certificates to the image usually resolves it in one line.
Cause 3: Corporate TLS Interception
Many corporate networks intercept TLS, presenting their own certificate signed by an internal CA. Browsers work because the CA is installed at the OS level through device management; command-line tools with their own trust stores do not.
The fix is to add the corporate root CA to the relevant trust store โ not to disable verification.
# Capture the certificate the proxy is presenting
openssl s_client -showcerts -connect example.com:443 </dev/null \
| openssl x509 -outform PEM > corp-ca.crt
# Inspect it to confirm what it is
openssl x509 -in corp-ca.crt -noout -subject -issuer -dates
# Install system-wide (Debian/Ubuntu)
sudo cp corp-ca.crt /usr/local/share/ca-certificates/corp-ca.crt
sudo update-ca-certificates
Ask your IT department for the official root CA file rather than extracting it yourself where possible โ extracting from a live connection assumes that connection is not already compromised.
Per-Tool Fixes
curl
# Point at a specific bundle
curl --cacert /path/to/ca-bundle.crt https://example.com
# Or set it for the session
export CURL_CA_BUNDLE=/path/to/ca-bundle.crt
Git
# Correct fix: point Git at a valid bundle
git config --global http.sslCAInfo /path/to/ca-bundle.crt
# Scope it to one host if only that host is affected
git config --global http."https://internal.company.com/".sslCAInfo /path/to/corp-ca.crt
git config --global http.sslVerify false appears in every search result for this error. It disables certificate verification for every host you ever clone from, permanently. Do not use it.
Node.js
# Add a CA to Node's trust store for this process
export NODE_EXTRA_CA_CERTS=/path/to/corp-ca.crt
node app.js
// Or per-request, which is more precise
import https from 'node:https';
import fs from 'node:fs';
const agent = new https.Agent({
ca: fs.readFileSync('/path/to/corp-ca.crt'),
});
await fetch('https://internal.example.com', { agent });
NODE_TLS_REJECT_UNAUTHORIZED=0 disables verification process-wide and Node prints a warning saying exactly that. It is acceptable in a throwaway local test and nowhere else.
Python
import certifi, requests
# Use certifi's bundle explicitly
requests.get('https://example.com', verify=certifi.where())
# Or a corporate CA
requests.get('https://internal.example.com', verify='/path/to/corp-ca.crt')
# Environment variables respected by requests and urllib3
export REQUESTS_CA_BUNDLE=/path/to/ca-bundle.crt
export SSL_CERT_FILE=/path/to/ca-bundle.crt
# Keep certifi current
pip install --upgrade certifi
Docker
FROM node:22-alpine
# Alpine ships without a CA bundle
RUN apk add --no-cache ca-certificates
# Add a corporate CA if you are behind interception
COPY corp-ca.crt /usr/local/share/ca-certificates/
RUN update-ca-certificates
ENV NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt
Cause 4: The Certificate Has Expired
Check before assuming a trust store problem.
echo | openssl s_client -connect example.com:443 2>/dev/null \
| openssl x509 -noout -dates
# notBefore=...
# notAfter=...
Also verify the client’s clock. A machine with a badly wrong date rejects valid certificates as not-yet-valid or expired, and this catches out containers and virtual machines that have been suspended.
date
timedatectl status # Linux
Diagnostic Sequence
openssl s_client -connect host:443 -showcertsโ read the verify return code- Count the certificates sent. One means a missing intermediate on the server.
- Check expiry dates and the local system clock.
- Compare against another machine or network โ if it works there, the issue is local.
- Check whether the issuer is your corporate CA, which indicates interception.
- Update
ca-certificatesand any language-specific bundle.
Why Not Just Disable Verification
Because certificate verification is the entire mechanism that prevents someone on the network from impersonating the server you think you are talking to. Disabling it means any intermediary can read and modify the traffic โ including credentials, tokens, and the packages you are downloading.
The realistic risk is not theoretical. A CI pipeline with verification disabled will happily install a package from an impersonated registry. The fix takes minutes; the consequence of skipping it can be a compromised build.
Frequently Asked Questions
Q: Why does it work in my browser but not in curl?
A: Browsers cache intermediate certificates from previous connections and can fetch missing ones automatically. curl does neither, so it exposes a server-side chain problem that browsers hide.
Q: What is the difference between error 20 and error 21?
A: 21 means the client could not verify the first certificate, typically because intermediates were not sent. 20 means the issuer was not found in your local trust store.
Q: Where is the CA bundle on my system?
A: Commonly /etc/ssl/certs/ca-certificates.crt on Debian and Ubuntu, /etc/pki/tls/certs/ca-bundle.crt on RHEL. In Python, python -m certifi prints the path certifi uses.
Q: Is it safe to disable verification just for local development?
A: For a local server with a self-signed certificate, add that certificate to your trust store instead โ it takes the same amount of time and does not create a habit that follows you into production configuration.
Q: My CI works but production fails. Why?
A: Almost always a difference in base image. Slim and Alpine images frequently omit ca-certificates entirely.
Conclusion
This error means the chain of trust is broken, and there are only a few causes: the server is not sending its intermediate certificates, your local CA bundle is missing or outdated, a corporate proxy is intercepting TLS with its own CA, or a certificate has expired. Diagnose with openssl s_client and the verify return code, then fix the actual cause โ install the CA, update ca-certificates, or serve the full chain. Never disable verification outside a throwaway local test, because that removes the protection the error exists to provide.
๐ You might also like
๐ Share this article




โ๏ธ Leave a Comment