Fix: SSL certificate problem: unable to get local issuer certificate
The Error
You run a git clone, curl, npm install, or any HTTPS request and get one of these:
Git:
fatal: unable to access 'https://github.com/user/repo.git/':
SSL certificate problem: unable to get local issuer certificatecurl:
curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.se/docs/sslcerts.htmlNode.js:
Error: unable to get local issuer certificate
at TLSSocket.onConnectSecure (node:_tls_wrap:1674:34)
code: 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY'Or one of these related errors:
SSL certificate problem: certificate has expiredError: CERT_HAS_EXPIREDERR_CERT_AUTHORITY_INVALIDSSL certificate problem: self signed certificate in certificate chainPython (requests/pip):
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed:
unable to get local issuer certificate (_ssl.c:1007)pip:
WARNING: Retrying (Retry(total=4)) after connection broken by
'SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED]
certificate verify failed: unable to get local issuer certificate'))':
/simple/package-name/All of these mean the same thing: the client cannot verify the server’s SSL/TLS certificate.
Why This Happens
When you connect to a server over HTTPS, the server sends its SSL certificate. Your client (Git, curl, Node.js, Python, a browser) must verify that certificate by tracing a chain of trust from the server’s certificate up through intermediate certificates to a root Certificate Authority (CA) that your system already trusts.
The verification fails when:
- Your system’s CA certificate bundle is outdated or missing. The root CA that signed the server’s certificate isn’t in your local trust store.
- The server’s certificate has expired. The certificate’s validity period has passed.
- The certificate chain is incomplete. The server isn’t sending the intermediate certificates, so your client can’t trace the chain back to a trusted root.
- A corporate proxy or firewall is intercepting HTTPS traffic. Your organization performs TLS inspection (MITM), replacing the server’s certificate with one signed by a corporate CA that your tools don’t trust.
- The certificate is self-signed. It wasn’t issued by any recognized CA.
- You’re inside a Docker container that ships with a minimal CA bundle or no CA bundle at all.
Fix 1: Update Your CA Certificate Bundle
The most common cause on Linux systems. Your CA certificates are outdated and don’t include the root CA that signed the server’s certificate.
Debian/Ubuntu:
sudo apt update && sudo apt install -y ca-certificates
sudo update-ca-certificatesRHEL/CentOS/Fedora:
sudo yum install -y ca-certificates
sudo update-ca-trustAlpine (common in Docker):
apk add --no-cache ca-certificates
update-ca-certificatesmacOS:
CA certificates come from the system Keychain. Update macOS to get the latest root certificates:
softwareupdate --install --allIf you’re using Homebrew’s OpenSSL or curl, make sure they can find the certificates:
brew install ca-certificatesWindows:
Windows manages root certificates automatically through Windows Update. Run Windows Update to get the latest root CAs.
If you’re using Git for Windows and it bundles its own CA file, update Git for Windows to the latest version.
Fix 2: Git – Configure the CA Bundle
Git uses its own SSL backend and sometimes can’t find the system CA bundle.
Find where Git looks for certificates:
git config --global http.sslCAInfoIf this is empty or points to a nonexistent file, set it to the correct path:
Linux:
git config --global http.sslCAInfo /etc/ssl/certs/ca-certificates.crtmacOS (Homebrew):
git config --global http.sslCAInfo "$(brew --prefix)/share/ca-certificates/cacert.pem"Or download an up-to-date CA bundle from curl’s website:
curl -o ~/cacert.pem https://curl.se/ca/cacert.pem
git config --global http.sslCAInfo ~/cacert.pemThe http.sslVerify false Shortcut (Use With Caution)
You’ll find this everywhere online:
git config --global http.sslVerify falseThis disables SSL verification entirely. Do not use this in production or on public networks. It makes you vulnerable to man-in-the-middle attacks. Use it only as a temporary diagnostic step to confirm the issue is certificate-related, then find the real fix.
If you must use it, scope it to a single repository instead of setting it globally:
git config http.sslVerify false # Only affects the current repoOr for a one-off clone:
GIT_SSL_NO_VERIFY=true git clone https://example.com/repo.gitFix 3: curl – Specify a CA Bundle or Diagnose
Check what CA bundle curl is using:
curl -vI https://example.com 2>&1 | grep CAfileSpecify a CA bundle manually:
curl --cacert /etc/ssl/certs/ca-certificates.crt https://example.comThe -k flag disables verification:
curl -k https://example.comSame warning as Git: -k / --insecure skips all certificate checks. Fine for debugging, dangerous for anything else. Never pipe curl -k output to sh or use it to download files you’ll execute.
Inspect the server’s certificate chain:
openssl s_client -connect example.com:443 -showcerts </dev/null 2>/dev/nullThis shows every certificate the server sends. Look for:
- Certificate chain completeness — you should see the server cert, intermediate cert(s), and optionally the root.
- Expiration dates — check
Not Afterfor each certificate. - The issuer — see if it’s a recognized CA or a corporate/self-signed issuer.
Fix 4: Node.js – Set the CA or Fix the Environment
Node.js uses its own compiled-in CA bundle (from Mozilla’s trust store), not the system’s. When you see UNABLE_TO_GET_ISSUER_CERT_LOCALLY in Node.js, the certificate’s CA isn’t in Node’s bundle.
Option A: Point Node.js to an extra CA file:
export NODE_EXTRA_CA_CERTS=/path/to/your/ca-bundle.crt
node app.jsThis adds certificates from the specified file to Node’s built-in CAs. This is the correct fix for corporate environments where a custom CA signs MITM certificates.
Option B: The nuclear option (do not use in production):
export NODE_TLS_REJECT_UNAUTHORIZED=0
node app.jsThis disables all certificate verification for every TLS connection in your Node.js process. This is dangerous. It means any certificate, including a malicious one, will be accepted. Never deploy code with this setting. It exists for debugging only.
If you see NODE_TLS_REJECT_UNAUTHORIZED in someone’s production code, that’s a security vulnerability.
Option C: Add the CA in code (for specific connections):
const https = require('https');
const fs = require('fs');
const ca = fs.readFileSync('/path/to/corporate-ca.crt');
https.get('https://internal.example.com', { ca }, (res) => {
// ...
});For npm specifically (see also Fix: npm EACCES Permission Denied for related npm issues):
npm config set cafile /path/to/your/ca-bundle.crtFix 5: Python / pip – SSL Certificate Verification
pip (if you’re also hitting build errors, see Fix: pip Could Not Build Wheels):
pip install --cert /path/to/ca-bundle.crt package-nameOr permanently:
pip config set global.cert /path/to/ca-bundle.crtPython requests library:
import requests
# Specify CA bundle
response = requests.get('https://example.com', verify='/path/to/ca-bundle.crt')
# Or disable verification (debugging only)
response = requests.get('https://example.com', verify=False)macOS Python from python.org:
Python installed from python.org on macOS doesn’t use the system certificates. Run the included script to install the certifi package:
/Applications/Python\ 3.x/Install\ Certificates.commandOr install certifi manually:
pip install certifi
python -c "import certifi; print(certifi.where())"This prints the path to the CA bundle that Python’s requests and urllib3 use. If you need to add a custom CA, append it to that file.
Fix 6: Docker – Add CA Certificates to Your Image
Minimal Docker base images (especially Alpine-based) often don’t include CA certificates.
Alpine:
FROM node:20-alpine
RUN apk add --no-cache ca-certificatesDebian/Ubuntu-based:
FROM python:3.12-slim
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*Adding a custom/corporate CA to a Docker image (for other Docker issues, see Fix: Docker COPY Failed: File Not Found):
FROM ubuntu:24.04
COPY corporate-ca.crt /usr/local/share/ca-certificates/corporate-ca.crt
RUN update-ca-certificatesThe certificate file must have a .crt extension and be in PEM format for update-ca-certificates to pick it up.
Fix 7: Corporate Proxy / Firewall MITM Certificates
Many corporate networks run a TLS-intercepting proxy (Zscaler, Fortinet, BlueCoat, etc.). The proxy terminates your HTTPS connection, inspects the traffic, then re-encrypts it with a certificate signed by the corporation’s own CA.
Your browser trusts this CA because IT pushed it to your system trust store. But Git, Node.js, Python, and curl may use their own CA bundles that don’t include it.
Step 1: Get the corporate CA certificate.
Ask your IT department for the root CA certificate in PEM format. Or extract it yourself:
# Connect to any HTTPS site through the proxy and grab the CA
openssl s_client -connect google.com:443 -showcerts </dev/null 2>/dev/null | \
awk '/BEGIN CERTIFICATE/,/END CERTIFICATE/{ print }' > chain.pemThe last certificate in the output is usually the root CA.
Step 2: Add it to your system trust store.
On Ubuntu/Debian:
sudo cp corporate-ca.crt /usr/local/share/ca-certificates/
sudo update-ca-certificatesOn RHEL/CentOS/Fedora:
sudo cp corporate-ca.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trustStep 3: Configure individual tools.
Even after adding to the system store, some tools need explicit configuration:
# Git
git config --global http.sslCAInfo /etc/ssl/certs/ca-certificates.crt
# npm
npm config set cafile /etc/ssl/certs/ca-certificates.crt
# pip
pip config set global.cert /etc/ssl/certs/ca-certificates.crt
# Node.js
export NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crtFix 8: Expired Certificates (Server-Side)
If the server’s certificate has expired, the fix is on the server side.
Check certificate expiration:
echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -datesOutput:
notBefore=Jan 1 00:00:00 2025 GMT
notAfter=Apr 1 00:00:00 2025 GMTIf notAfter is in the past, the certificate has expired.
Renew with Let’s Encrypt (certbot):
sudo certbot renew
sudo systemctl reload nginx # or apache2 / httpdIf auto-renewal isn’t set up, add it:
sudo certbot renew --dry-run # Test firstThen add a cron job or systemd timer:
# /etc/cron.d/certbot
0 0,12 * * * root certbot renew --quiet --post-hook "systemctl reload nginx"Let’s Encrypt root CA changes: In 2021, Let’s Encrypt switched from the DST Root CA X3 (cross-signed) to the ISRG Root X1 root. Older systems that don’t have ISRG Root X1 in their trust store will fail to verify Let’s Encrypt certificates. The fix is to update your CA certificates (Fix 1).
Fix 9: Certificate Chain Order (Server Misconfiguration)
The server must send certificates in order: server cert first, then each intermediate up to (but not necessarily including) the root. If the order is wrong or intermediates are missing, clients can’t build the trust chain.
Test your certificate chain:
openssl s_client -connect example.com:443 </dev/null 2>/dev/nullLook for Verify return code:
0 (ok)— chain is valid.21 (unable to verify the first certificate)— missing intermediate certificate.10 (certificate has expired)— expired certificate in the chain.
Check with an online tool: SSL Labs Server Test gives a detailed chain analysis and flags missing intermediates.
Fix the chain in Nginx:
Your certificate file should contain the server certificate followed by the intermediate(s):
cat server.crt intermediate.crt > fullchain.crtIn nginx.conf:
ssl_certificate /etc/ssl/fullchain.crt;
ssl_certificate_key /etc/ssl/server.key;Fix the chain in Apache:
SSLCertificateFile /etc/ssl/server.crt
SSLCertificateChainFile /etc/ssl/intermediate.crt
SSLCertificateKeyFile /etc/ssl/server.keyLet’s Encrypt’s certbot handles this automatically — the fullchain.pem file includes both the server cert and the intermediate.
Fix 10: Self-Signed Certificates in Development
Self-signed certificates are not signed by any CA, so every client will reject them by default. This is normal and expected.
Generate a self-signed certificate for local development:
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes \
-subj "/CN=localhost"Trust it on your machine:
On macOS:
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain cert.pemOn Ubuntu/Debian:
sudo cp cert.pem /usr/local/share/ca-certificates/localhost-dev.crt
sudo update-ca-certificatesOn Windows, double-click the .crt file, install it to the “Trusted Root Certification Authorities” store.
Better alternative for local dev: Use mkcert to create locally-trusted development certificates. It installs a local CA in your system trust store and generates certificates signed by that CA:
mkcert -install
mkcert localhost 127.0.0.1 ::1This creates localhost+2.pem and localhost+2-key.pem that are automatically trusted by your browsers and system tools. No more SSL errors in development.
Still Not Working?
Stale certificate cache. Some applications cache SSL sessions and certificates. Restart the application, clear browser cache, or restart your shell session after updating CA certificates.
Wrong system time. SSL certificate validation checks the current date against the certificate’s validity period. If your system clock is significantly off, valid certificates appear expired. Check with
dateand sync with NTP:sudo timedatectl set-ntp trueSNI (Server Name Indication) issues. If the server hosts multiple domains on one IP, older clients that don’t send SNI may receive the wrong certificate. Test with:
openssl s_client -connect example.com:443 -servername example.com </dev/nullThe
-servernameflag sends the SNI header.VPN or proxy overriding DNS. Your VPN might resolve the hostname to an internal IP that serves a different certificate. Check what IP you’re actually connecting to:
dig +short example.com curl -v https://example.com 2>&1 | grep "Connected to"The
SSL_CERT_FILEorSSL_CERT_DIRenvironment variables are set incorrectly. Some tools respect these OpenSSL environment variables. If they point to a nonexistent or incomplete CA bundle, verification fails even though the system store is fine:echo $SSL_CERT_FILE echo $SSL_CERT_DIRUnset them to use the defaults, or point them to the correct location.
WSL2 doesn’t share Windows certificate stores. If you’re running Git, curl, or Node.js inside WSL2, the Linux environment has its own CA store separate from Windows. You need to add corporate or custom CAs inside WSL2 as well. See Fix 7 for how to add certificates to the Linux trust store.
Java/JVM applications use their own trust store. Java doesn’t use the system CA bundle. It uses a
cacertskeystore file. Add your CA with:keytool -importcert -alias corporate-ca -file corporate-ca.crt \ -keystore $JAVA_HOME/lib/security/cacerts -storepass changeit -nopromptCertificate pinning. Some applications pin specific certificates or public keys. Even if the certificate is valid, if it doesn’t match the pinned value, the connection fails. This is common in mobile apps and some security-conscious services. You can’t fix this by updating CA bundles — the application itself needs to be updated.
OCSP stapling failures. If the server has OCSP stapling enabled but the stapled response is invalid or expired, some clients reject the connection. Check with:
openssl s_client -connect example.com:443 -status </dev/null 2>/dev/null | grep -A 5 "OCSP"On the server side (Nginx), ensure OCSP stapling is configured correctly:
ssl_stapling on; ssl_stapling_verify on; resolver 8.8.8.8 8.8.4.4 valid=300s;
Related: If your Git connection fails with an SSH key issue instead of SSL, see Fix: Permission denied (publickey). If your Nginx reverse proxy returns a 502 after configuring SSL, see Fix: Nginx 502 Bad Gateway. For connection issues to local development servers, see Fix: ERR_CONNECTION_REFUSED on localhost.
Related Articles
Fix: curl: (7) Failed to connect / (6) Could not resolve host / (28) Operation timed out
How to fix curl errors including 'Failed to connect to host', 'Could not resolve host', 'Operation timed out', and 'SSL certificate problem'. Covers curl exit codes 6, 7, 28, 35, 56, and 60, DNS resolution, proxy settings, timeout tuning, SSL issues, retry strategies, verbose debugging, and more.
Fix: Docker Volume Permission Denied – Cannot Write to Mounted Volume
How to fix Docker permission denied errors on mounted volumes caused by UID/GID mismatch, read-only mounts, or SELinux labels.
Fix: E: Unable to locate package (apt-get install on Ubuntu/Debian)
How to fix the 'E: Unable to locate package' error in apt-get on Ubuntu and Debian, including apt update, missing repos, Docker images, PPA issues, and EOL releases.
Fix: Docker no space left on device (build, pull, or run)
How to fix the 'no space left on device' error in Docker when building images, pulling layers, or running containers, with cleanup and prevention strategies.