Skip to main content
DevOpsintermediate

Fix "unable to get local issuer certificate" (SSL)

Fix the SSL error "unable to get local issuer certificate" in curl, git, Node, and Python. Diagnose an incomplete server chain, repair the CA bundle, and trust a corporate TLS proxy root properly.

10 min readUpdated August 2026

This error means one thing: your client got a certificate but could not build a trusted chain from it up to a root it already knows.

curl: (60) SSL certificate problem: unable to get local issuer certificate
fatal: unable to access 'https://github.com/user/repo.git/':
  SSL certificate problem: unable to get local issuer certificate
Error: unable to get local issuer certificate
    code: 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY'
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify
  failed: unable to get local issuer certificate (_ssl.c:1006)

Same failure, four tools. There are exactly three causes, and one command tells you which you have.


Step 1: Find Out Whose Fault It Is

openssl s_client -connect example.com:443 -showcerts </dev/null 2>/dev/null | grep -E 's:|i:'

This prints the certificates the server actually sent, with subject (s:) and issuer (i:) for each.

  • Only one certificate came back, and its issuer is not a well-known root → the server is not sending its intermediate. Go to Cause A.
  • Two or more came back (leaf, then intermediate) but verification still fails → the problem is on your machine. Go to Cause B.
  • The issuer is your employer, an antivirus vendor, or a firewall product (Zscaler, Netskope, Palo Alto, Kaspersky, ESET) → TLS interception. Go to Cause C.

You can also just ask openssl to verify:

openssl s_client -connect example.com:443 </dev/null 2>/dev/null | grep -i "verify"

Verify return code: 0 (ok) means the chain is fine and something tool-specific is wrong.


Cause A: The Server Sends an Incomplete Chain

A public certificate is normally signed by an intermediate CA, which is signed by a root. Your machine trusts the root, not the intermediate — so the server must send the intermediate itself. If it sends only the leaf, the chain has a hole in it.

Browsers often hide this, because most of them fetch the missing intermediate automatically via the certificate's AIA extension. Command-line tools do not. So "it works in Chrome" is not evidence that the server is correct.

Fix it on the server. In nginx, ssl_certificate must point at the full chain, not the leaf:

# wrong — leaf only
ssl_certificate     /etc/letsencrypt/live/example.com/cert.pem;

# right — leaf + intermediate
ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

With Certbot this is the single most common mistake: cert.pem is the leaf alone, fullchain.pem is what you want. Reload and re-test:

sudo nginx -t && sudo systemctl reload nginx
openssl s_client -connect example.com:443 -showcerts </dev/null 2>/dev/null | grep -c "BEGIN CERTIFICATE"

You should now see at least 2.

If it is not your server, you cannot fix it properly — report it to whoever runs it. Supplying the intermediate locally is a workaround, not a fix, and it will break for everyone else.


Cause B: Your CA Bundle Is Missing or Outdated

Common in containers, minimal images, and long-lived servers that have not been patched.

Debian / Ubuntu:

sudo apt-get update
sudo apt-get install --reinstall ca-certificates
sudo update-ca-certificates

Bundle location: /etc/ssl/certs/ca-certificates.crt

RHEL / Rocky / AlmaLinux / Fedora:

sudo dnf reinstall ca-certificates
sudo update-ca-trust extract

Bundle location: /etc/pki/tls/certs/ca-bundle.crt

Alpine (very frequently the cause in slim Docker images, which ship without a CA bundle at all):

apk add --no-cache ca-certificates
update-ca-certificates

macOS: the system store is managed by Keychain Access, but Homebrew's curl and openssl may use their own. Check with curl-config --ca or openssl version -d.

This category also covers root expiry. When DST Root CA X3 expired in 2021, machines with outdated bundles produced this exact error against Let's Encrypt sites, and updating ca-certificates was the fix.


Advertisement

Cause C: A TLS-Inspecting Proxy

Corporate firewalls, secure web gateways, and some antivirus products decrypt HTTPS and re-sign it with a private root certificate. Your browser trusts that root because IT installed it into the OS or browser store; your command-line tools do not.

The fix is to trust that root properly — not to disable verification.

Get the root from your IT team, or extract it:

openssl s_client -connect example.com:443 -showcerts </dev/null 2>/dev/null \
  | openssl x509 -noout -issuer

Install it, Debian / Ubuntu — the file must be PEM format with a .crt extension:

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

Install it, RHEL / Rocky / AlmaLinux:

sudo cp corp-root.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust extract

Tools That Ignore the System Store

Updating the system bundle fixes curl and git. Several runtimes carry their own trust store and need to be told separately.

ToolHow to point it at the right CA
curlcurl --cacert /path/to/ca-bundle.crt https://…
gitgit config --global http.sslCAInfo /etc/ssl/certs/ca-certificates.crt
Node.jsexport NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/corp-root.crt
Python requestsexport REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
Python (stdlib ssl)export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
Javakeytool -importcert -alias corp-root -file corp-root.crt -keystore "$JAVA_HOME/lib/security/cacerts"

Python's requests uses the certifi bundle, not the system store. Find the file it is really reading:

python -m certifi

The Insecure Bypass — and Why Not To Use It

You will find these everywhere online:

curl -k https://example.com                # INSECURE
export GIT_SSL_NO_VERIFY=true              # INSECURE
export NODE_TLS_REJECT_UNAUTHORIZED=0      # INSECURE

These do not fix anything. They switch off the check that tells you whether you are talking to the real server. With verification disabled, an attacker who can intercept your traffic can present any certificate they like and you will never know — which matters most in exactly the situation that produced this error, since something already is intercepting your TLS.

Acceptable use is narrow: a one-off command on a machine you control, while diagnosing, typed inline rather than exported. Never in CI, never in a Dockerfile, never in a deployment script, never exported into a shell profile where it silently applies to everything you run afterwards.


Verify the Fix

curl -vI https://example.com 2>&1 | grep -i "SSL certificate verify"

Expected:

* SSL certificate verify ok.

For git:

git ls-remote https://github.com/user/repo.git

You can also verify a chain offline:

openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt server-chain.pem

Prevention

  • Always deploy fullchain.pem, never the bare leaf, and re-test with openssl s_client after every renewal.
  • Install ca-certificates in your base images. Alpine and scratch-derived images ship without it, and the failure only appears the first time the container makes an outbound HTTPS call.
  • Distribute the corporate root through configuration management, so new machines and containers trust it from first boot rather than each developer discovering this error individually.
  • Monitor certificate expiry rather than waiting for the error — chain problems and expiries both surface as failed builds at the worst moment.

Frequently Asked Questions

Find answers to common questions

Your client received a certificate but could not build a complete chain from it up to a root it already trusts. Either the server did not send the intermediate certificate, your local CA bundle is missing or outdated, or something is intercepting TLS and re-signing traffic with a private root you do not trust.

Run 'openssl s_client -connect example.com:443 -showcerts </dev/null' and count the certificates returned. If only one comes back for a certificate issued by an intermediate CA, the server is not sending its chain. If the full chain comes back and verification still fails, the problem is local.

No. Those disable certificate verification entirely, which removes the only protection against an impostor server and makes a man-in-the-middle attack undetectable. Use them only as a momentary diagnostic on a machine you control, never in CI, scripts, containers, or production.

Run 'sudo apt-get install --reinstall ca-certificates' followed by 'sudo update-ca-certificates'. The combined bundle is written to /etc/ssl/certs/ca-certificates.crt. Long-running containers and old base images are the usual culprits for a stale bundle.

On Debian and Ubuntu, copy the PEM file into /usr/local/share/ca-certificates/ with a .crt extension, then run 'sudo update-ca-certificates'. On RHEL, Rocky, and AlmaLinux, copy it into /etc/pki/ca-trust/source/anchors/ and run 'sudo update-ca-trust extract'.

Browsers ship their own trust store and many of them will fetch a missing intermediate automatically using the AIA extension. Command-line tools use the system CA bundle and do not chase missing intermediates, so a server with an incomplete chain works in Chrome and fails in curl. The server is still misconfigured.

Point ssl_certificate at the full chain file rather than the leaf certificate. With Certbot that means fullchain.pem, not cert.pem. Reload nginx and re-test with openssl s_client — you should now see the leaf and at least one intermediate.

Set NODE_EXTRA_CA_CERTS to the PEM file containing the extra root, for example 'export NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/corp-root.crt'. Node bundles its own root store and does not read the system trust store on every platform, which is why it fails when curl succeeds.

Python's requests uses the certifi bundle rather than the system store. Point it at the right file with REQUESTS_CA_BUNDLE or SSL_CERT_FILE, or append your root to the certifi bundle. Run 'python -m certifi' to print the path requests is actually using.

Not this specific message. An expired leaf produces a certificate-expired error instead. However, an expired or newly retired root in your local bundle can break chain building — the DST Root CA X3 expiry in 2021 broke this exact way on machines with old bundles, and the fix was updating ca-certificates.