Fix "unable to get local issuer certificate" in git, curl, pip, and npm in 5 minutes
If git clone worked yesterday and suddenly stopped
git clone that worked fine yesterday suddenly dies this morning with:
fatal: unable to access 'https://github.com/...': SSL certificate problem:
unable to get local issuer certificatecurl says this:
curl: (60) SSL certificate problem: self signed certificate in certificate chainAnd pip throws a tantrum like this:
SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED]
certificate verify failed: unable to get local issuer certificate'))The wording differs by tool, but the root cause is the same: the client could not verify the server certificate's issuer (CA) against its trust chain. Especially since 2026, SSL inspection (MITM) proxies like Zscaler and Netskope have become the corporate default, so this error is now a rite of passage for any developer who logs onto the office network.
This post goes in order: get the build running first → understand why → finish with the proper team-wide fix.
1. Start by telling the four root causes apart
Subtle differences in the error message tell you the cause.
| Cause | Typical error message | One-line explanation |
|---|---|---|
| ① Missing or expired CA bundle | unable to get local issuer certificate | Client has no root CA, or an outdated ca-bundle |
| ② Corporate proxy (MITM) | self signed certificate in certificate chain | Zscaler etc. intercept traffic and re-sign with their own root |
| ③ Self-signed certificate | self signed certificate | Internal GitLab/Nexus issued its own cert |
| ④ Wrong system clock | certificate has expired / not yet valid | PC clock is wrong, so validity checks fail |
The key distinction: unable to get local issuer certificate means "I cannot find the issuer" (missing intermediate CA). self signed certificate in certificate chain means "the top of the chain is an untrusted self-signed cert" (MITM proxy or internal cert). If you see the latter, it is almost certainly a company proxy or an internal server.
2. Diagnostic commands: don't guess—inspect the certificate chain
First, see which certificates are actually being presented.
# Inspect the full certificate chain and issuer
openssl s_client -connect github.com:443 -showcerts </dev/null 2>/dev/null \
| openssl x509 -noout -issuer -subject -datesIf issuer shows Zscaler Root CA or your company name → MITM proxy confirmed. If github.com is issued by your company, traffic is being re-signed in the middle.
# Peek at curl's verification steps
curl -v https://github.com 2>&1 | grep -E "issuer|subject|SSL certificate|verify"# Compare system time vs. certificate validity (check cause ④)
date
openssl s_client -connect github.com:443 </dev/null 2>/dev/null \
| openssl x509 -noout -datesIf notBefore is in the future, fix the PC clock first (sudo ntpdate / w32tm /resync).
3. Copy-paste fixes by tool (get unblocked first)
Assume the root certificate from IT (or extracted in step 4) is corp-ca.pem.
git
# Global
git config --global http.sslCAInfo /etc/ssl/certs/corp-ca.pem
# One repository only
git config http.https://gitlab.company.com.sslCAInfo /etc/ssl/certs/corp-ca.pemcurl
curl --cacert /etc/ssl/certs/corp-ca.pem https://github.com
# Apply globally via env var
export CURL_CA_BUNDLE=/etc/ssl/certs/corp-ca.pempip — pip.conf (Linux/macOS: ~/.pip/pip.conf, Windows: %APPDATA%\pip\pip.ini)
[global]
cert = /etc/ssl/certs/corp-ca.pem# One-off
pip install --cert /etc/ssl/certs/corp-ca.pem requests
# Global for requests-family libraries
export REQUESTS_CA_BUNDLE=/etc/ssl/certs/corp-ca.pem
export SSL_CERT_FILE=/etc/ssl/certs/corp-ca.pemnpm / node
npm config set cafile /etc/ssl/certs/corp-ca.pem
# Node runtime global (Yarn, fetch, etc.)
export NODE_EXTRA_CA_CERTS=/etc/ssl/certs/corp-ca.pemEnv vars (
REQUESTS_CA_BUNDLE,NODE_EXTRA_CA_CERTS,SSL_CERT_FILE) are especially useful in CI/CD runners and Docker containers. Inject them once in theDockerfileor runner environment instead of configuring every tool.
4. Permanently install the CA by OS (do it once, properly)
If you don't want to pass flags every time, register the cert in the system trust store.
| OS | Certificate location | Apply command |
|---|---|---|
| Ubuntu/Debian | /usr/local/share/ca-certificates/corp-ca.crt | sudo update-ca-certificates |
| RHEL/CentOS | /etc/pki/ca-trust/source/anchors/corp-ca.crt | sudo update-ca-trust |
| macOS | System keychain | sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain corp-ca.pem |
| Windows | Trusted Root store | certutil -addstore -f "ROOT" corp-ca.crt (or certmgr.msc) |
Note: even after a system install, Python (certifi), Node, and Git for Windows often ship their own CA bundles, so you may still need the step-3 settings as well.
Extracting a corporate proxy root cert (Zscaler, etc.)
Asking IT is the most reliable path, but you can pull it yourself.
# Save the root cert the proxy re-signed with
openssl s_client -connect github.com:443 -showcerts </dev/null 2>/dev/null \
| openssl x509 -outform PEM > corp-ca.pemYou can also click the padlock in the browser → View certificate → export the top of the chain (Zscaler Root CA) as Base64 (.pem). Register that file with the step 3 and 4 flow above.
5. Please do not use sslVerify=false
Turning verification off with the commands below because you're in a hurry throws the door wide open to MITM attacks.
⚠️ Danger — no temporary workarounds
Bashgit config --global http.sslVerify false # Disables all HTTPS verification pip install --trusted-host pypi.org ... # No protection against tampered packages npm config set strict-ssl false # Exposed to supply-chain attacksWith verification off, you would never know if someone swapped the code in transit. Replace this with the proper approach above: register the correct CA.
A note from the field
After an SSL inspection proxy landed internally, I watched a team of dozens of developers each bypass it with sslVerify false. The actual fix was simple: put the company root cert in the internal package repo, and ship a one-liner CA install script in standard dotfiles and the base Docker image. SSL tickets almost disappeared after that. Having the team install it once is far cheaper than every individual turning verification off for five minutes.
Decision checklist
openssl s_client ... -issuer→ issuer is a company name? → MITM proxy; extract and register the root CA- Issuer looks normal but you still get the error? → stale CA bundle; run
update-ca-certificates/ refresh certifi - Hitting internal GitLab/Nexus? → self-signed cert; set sslCAInfo for that host only
expired/not yet valid? → sync the system clock
FAQ
Q. I registered the CA and still get the error.
A. The tool is not looking at the system store. For Python, use certifi (check the path with python -m certifi, then set REQUESTS_CA_BUNDLE). For Node, set NODE_EXTRA_CA_CERTS. For Git for Windows, set http.sslCAInfo separately. Also confirm the cert is PEM (Base64).
Q. SSL errors only happen inside Docker containers.
A. Containers do not inherit the host CA store. In the Dockerfile, COPY corp-ca.crt /usr/local/share/ca-certificates/ then RUN update-ca-certificates, or inject NODE_EXTRA_CA_CERTS / REQUESTS_CA_BUNDLE as build args.
Q. It only fails on the corporate network; it works from home. A. Classic SSL inspection proxy (Zscaler, etc.). On the company network, traffic is re-signed, so you must trust the company root CA. Checking the issuer will show the company name.
Q. The error appears when I turn VPN on.
A. The proxy is inserted over the VPN path and the certificate changes. Compare issuers with openssl s_client with VPN on and off, then register the proxy root CA on the system so both paths work.
Nodelog는 모든 콘텐츠의 내용과 출처를 공개 전에 검토합니다. 환경(OS·버전)에 따라 결과가 달라질 수 있는 기술 정보는 공식 문서와 함께 확인하며, 검토 기준과 정정 원칙은 편집 정책에서 안내합니다. 오류를 발견하시면 이메일로 제보해 주세요 — 확인 후 신속히 정정합니다.
Comments
Be the first to comment.