TLS & Security Error Guide: 'unable to get local issuer certificate' (Chain / CA)
Fix 'SSL certificate problem: unable to get local issuer certificate': repair an incomplete chain, missing intermediate, or untrusted CA bundle on the client.
- #security
- #troubleshooting
- #errors
- #tls
Overview
This error means the client built a partial certificate chain but could not find the issuer of one of the certificates it received, so it cannot reach a trusted root. It is almost never an “expired” or “wrong hostname” problem — it is a chain completeness or trust store problem. Either the server failed to send the intermediate certificate, or the client’s CA bundle does not contain the root that signs the chain.
You will see it from OpenSSL, curl, git, and most TLS clients:
SSL certificate problem: unable to get local issuer certificate
OpenSSL reports the same condition as a numeric verify code:
Verify return code: 20 (unable to get local issuer certificate)
Code 20 means “I could not find the issuer of the leaf” (usually a missing intermediate), while code 21 (“unable to verify the first certificate”) and code 19 (“self-signed cert in chain / untrusted root”) are closely related variants of the same trust gap.
Symptoms
curl,git,wget, or a language HTTP client fails with “unable to get local issuer certificate”.- A browser works (browsers cache/fetch intermediates) but CLI tools fail — a classic sign of a missing intermediate.
openssl s_clientprintsVerify return code: 20or21.- The same endpoint validates from one machine but not another (different CA bundles).
echo | openssl s_client -connect repo.internal.example.com:443 -servername repo.internal.example.com 2>/dev/null \
| grep -E 'Verify return code|s:|i:'
0 s:CN = repo.internal.example.com
i:C = US, O = Example, CN = Example Intermediate CA R3
Verify return code: 20 (unable to get local issuer certificate)
The chain stops at the leaf (depth 0) — no intermediate was sent.
Common Root Causes
1. The server does not send the intermediate certificate
The server is configured with only the leaf (cert.pem) instead of the full chain (fullchain.pem). Browsers paper over this via AIA fetching; CLI clients do not.
echo | openssl s_client -connect repo.internal.example.com:443 -showcerts 2>/dev/null \
| grep -c 'BEGIN CERTIFICATE'
1
Only one certificate was presented — the intermediate is missing.
2. The signing root is not in the client’s trust store
The chain is complete, but the root that anchors it (e.g., an internal/private CA) is not installed on the client.
echo | openssl s_client -connect repo.internal.example.com:443 -showcerts 2>/dev/null \
| openssl storeutl -noout -text -certs /dev/stdin 2>/dev/null \
| grep -E 'Issuer:|Subject:'
Subject: CN = repo.internal.example.com
Issuer: CN = Example Intermediate CA R3
Subject: CN = Example Intermediate CA R3
Issuer: CN = Example Internal Root CA
The chain ends at Example Internal Root CA. If that root is not in /etc/ssl/certs, verification fails with code 19/20.
3. Out-of-order or duplicated chain file
The fullchain.pem has the certs in the wrong order, or the leaf is missing, so the client cannot link leaf -> intermediate -> root.
openssl crl2pkcs7 -nocrl -certfile /etc/nginx/ssl/fullchain.pem 2>/dev/null \
| openssl pkcs7 -print_certs -noout 2>/dev/null
subject=CN = Example Intermediate CA R3
subject=CN = repo.internal.example.com
The intermediate appears before the leaf — most servers require leaf first, then intermediates.
4. A stale or trimmed system CA bundle on the client
A minimal container image (alpine, scratch, distroless) ships without ca-certificates, or the bundle was pruned.
ls -l /etc/ssl/certs/ca-certificates.crt 2>/dev/null || echo "missing"
missing
No system bundle means every public CA fails with code 20.
5. The intermediate expired or was cross-signed differently
The server sends an intermediate whose own issuer (a cross-signing root) is no longer trusted, so the chain dead-ends at an unknown authority.
echo | openssl s_client -connect repo.internal.example.com:443 -showcerts 2>/dev/null \
| openssl storeutl -noout -text -certs /dev/stdin 2>/dev/null \
| grep -E 'Subject:|Issuer:|Not After'
Subject: CN = Example Intermediate CA R3
Issuer: CN = Legacy Cross-Sign Root X1
If Legacy Cross-Sign Root X1 is not in the trust store, the chain is incomplete.
6. Client uses a custom CA path that does not include the issuer
Tools honor CURL_CA_BUNDLE, SSL_CERT_FILE, GIT_SSL_CAINFO, or REQUESTS_CA_BUNDLE pointing at the wrong file.
env | grep -iE 'CA_BUNDLE|SSL_CERT|CAINFO'
REQUESTS_CA_BUNDLE=/opt/app/certs/old-bundle.pem
An override pointing at an outdated bundle silently breaks verification for that tool only.
Diagnostic Workflow
Step 1: Get the exact verify code
echo | openssl s_client -connect <HOST>:443 -servername <HOST> 2>/dev/null | grep 'Verify return code'
Code 20 = missing issuer (usually missing intermediate); code 19 = self-signed/untrusted root in chain.
Step 2: Count how many certs the server actually sends
echo | openssl s_client -connect <HOST>:443 -servername <HOST> -showcerts 2>/dev/null \
| grep -c 'BEGIN CERTIFICATE'
1 for a publicly-signed leaf almost always means a missing intermediate.
Step 3: Walk the chain to find where trust breaks
echo | openssl s_client -connect <HOST>:443 -servername <HOST> -showcerts 2>/dev/null \
| openssl storeutl -noout -text -certs /dev/stdin 2>/dev/null \
| grep -E 'Subject:|Issuer:'
The last Issuer: that has no matching Subject: is the missing/untrusted link.
Step 4: Verify against the client’s actual trust store
echo | openssl s_client -connect <HOST>:443 -servername <HOST> -showcerts 2>/dev/null \
| openssl x509 > /tmp/leaf.pem
openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt -untrusted /tmp/chain.pem /tmp/leaf.pem
/tmp/leaf.pem: OK
If you must pass -untrusted to make it pass, the server is not sending the intermediate.
Step 5: Fix the server chain or install the root, then re-verify
# Server side: serve full chain
cat leaf.pem intermediate.pem > /etc/nginx/ssl/fullchain.pem
sudo systemctl reload nginx
# Client side (private root): trust it
sudo cp internal-root.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates
echo | openssl s_client -connect <HOST>:443 -servername <HOST> 2>/dev/null | grep 'Verify return code'
Example Root Cause Analysis
A CI job that runs git clone https://repo.internal.example.com/app.git started failing, but developers’ browsers open the repo UI fine:
fatal: unable to access 'https://repo.internal.example.com/app.git/': SSL certificate problem: unable to get local issuer certificate
The browser working but git failing is the tell: browsers fetch missing intermediates via AIA, git does not. Counting served certs confirms it:
echo | openssl s_client -connect repo.internal.example.com:443 -showcerts 2>/dev/null \
| grep -c 'BEGIN CERTIFICATE'
1
Only the leaf is served. The repo server was configured with ssl_certificate cert.pem; (leaf only) after a recent cert swap, dropping the intermediate that the previous fullchain.pem had included.
Fix: rebuild the full chain and reload:
cat cert.pem intermediate.pem > fullchain.pem
# point ssl_certificate at fullchain.pem, then:
sudo nginx -t && sudo systemctl reload nginx
echo | openssl s_client -connect repo.internal.example.com:443 2>/dev/null | grep 'Verify return code'
Verify return code: 0 (ok)
git clones succeed without any client-side change.
Prevention Best Practices
- Always configure servers with the full chain (
fullchain.pem), leaf first, intermediates after — never the bare leaf. - After every cert renewal, re-test with a CLI client (
openssl s_client/curl), not just a browser, since browsers hide missing intermediates. - Ship
ca-certificatesin minimal/container images, and install internal roots into the system store rather than per-toolCA_BUNDLEoverrides. - Audit
SSL_CERT_FILE/CURL_CA_BUNDLE/REQUESTS_CA_BUNDLEenv vars — stray overrides silently break one tool while others work. - Document your private CA hierarchy and distribute the root via config management; see the security hardening guides for a trust-store rollout pattern.
- When an outage hits CLI tools but not browsers, the free incident assistant can confirm a missing-intermediate signature from the
s_clientoutput.
Quick Command Reference
# Verify code (20 = missing issuer)
echo | openssl s_client -connect <HOST>:443 -servername <HOST> 2>/dev/null | grep 'Verify return code'
# How many certs does the server send?
echo | openssl s_client -connect <HOST>:443 -showcerts 2>/dev/null | grep -c 'BEGIN CERTIFICATE'
# Walk subject/issuer to find the broken link
echo | openssl s_client -connect <HOST>:443 -showcerts 2>/dev/null \
| openssl storeutl -noout -text -certs /dev/stdin 2>/dev/null | grep -E 'Subject:|Issuer:'
# Check tool-specific CA overrides
env | grep -iE 'CA_BUNDLE|SSL_CERT|CAINFO'
# Trust a private root
sudo cp internal-root.crt /usr/local/share/ca-certificates/ && sudo update-ca-certificates
# Rebuild the server full chain
cat leaf.pem intermediate.pem > fullchain.pem
Conclusion
unable to get local issuer certificate is a chain-completeness or trust problem, not an expiry or hostname problem. The usual root causes:
- The server sends only the leaf and omits the intermediate (browsers hide this, CLI tools do not).
- The anchoring root (often a private CA) is not in the client’s trust store.
- The chain file is out of order or missing the leaf.
- A minimal/container image ships without
ca-certificates. - An intermediate chains through a cross-signing root that the client does not trust.
- A
CA_BUNDLE/SSL_CERT_FILEoverride points at the wrong bundle for one tool.
Count the certs the server sends and walk subject/issuer to the first unresolved link — that single missing or untrusted certificate is almost always the entire fix.
Download the Free 500-Prompt DevOps AI Toolkit
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.