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.
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.
| Tool | How to point it at the right CA |
|---|---|
| curl | curl --cacert /path/to/ca-bundle.crt https://… |
| git | git config --global http.sslCAInfo /etc/ssl/certs/ca-certificates.crt |
| Node.js | export NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/corp-root.crt |
| Python requests | export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt |
| Python (stdlib ssl) | export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt |
| Java | keytool -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 withopenssl s_clientafter every renewal. - Install
ca-certificatesin your base images. Alpine andscratch-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.