“unable to get local issuer certificate”: why it happens and how to fix it properly
Your program got a certificate from the server, tried to follow the chain of signatures up to a root it trusts, and got stuck: it could not find the certificate that signed the last one. The website may be perfectly fine; the gap is usually on your side.
What the error looks like
curl: (60) SSL certificate problem: unable to get local issuer certificate
requests.exceptions.SSLError: ... [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate
Error: unable to get local issuer certificate (code: 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY')
npm ERR! code UNABLE_TO_GET_ISSUER_CERT_LOCALLY
fatal: unable to access 'https://…': SSL certificate problem: unable to get local issuer certificate
A close cousin is self signed certificate in certificate chain (SELF_SIGNED_CERT_IN_CHAIN): the chain did reach a root, but it’s a root you don’t trust. Both have the same fixes.
How the chain is supposed to work
A server sends its own certificate (the leaf) plus one or more intermediate certificates. Each one is signed by the next, ending at a root certificate that ships inside your operating system or your tool. “Local issuer” means “the issuer I have locally”: the client walked up the chain and the next certificate it needed was neither sent by the server nor in its local store.
The four usual causes
| Cause | Clue | Fix |
|---|---|---|
| A company proxy, VPN or antivirus inspects HTTPS | Fails on every site at work, works at home; the issuer is your company or security product, not a public CA | Get the company root certificate (IT usually publishes it) and add it to each tool’s trust store (below) |
| No CA certificates in the container or minimal OS | Works on your laptop, fails in a slim Docker image or a fresh server | apt-get install -y ca-certificates (Debian/Ubuntu) or apk add ca-certificates (Alpine) |
| The tool uses its own CA bundle, not the OS one | Browser and curl work, but Python, Node or Java fail | Point that tool at the right bundle, or install the macOS Python certificates (below) |
| The server doesn’t send its intermediate | Fails for everyone on that one site, often only in scripts (browsers sometimes fetch or cache missing intermediates) | Server owner: serve the full chain file (for example fullchain.pem, not cert.pem) |
See the chain for yourself
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null
Read the s: (subject) and i: (issuer) lines from the top. If the last certificate’s issuer is something like Corp-Proxy Root CA or your antivirus, it’s interception. If the server sent only one certificate and the output ends with Verify return code: 20 (unable to get local issuer certificate) while other sites work, the server is missing its intermediate. Each certificate between the BEGIN and END lines is just Base64 text; paste one into the What is this? detector or the Base64 decoder to see it isn’t secret.
Trust the extra root, tool by tool
Save the root certificate as a PEM file (text starting with -----BEGIN CERTIFICATE-----), then:
- Whole machine (Debian/Ubuntu): copy it to
/usr/local/share/ca-certificates/corp-root.crtand runsudo update-ca-certificates. On RHEL/Fedora:/etc/pki/ca-trust/source/anchors/thensudo update-ca-trust. curl, git and most system tools then trust it. - Node.js and npm: Node ships its own list of roots and ignores the OS store by default. Set
NODE_EXTRA_CA_CERTS=/path/corp-root.pem(it adds to the built-in list). For npm also:npm config set cafile /path/corp-root.pem. Recent Node versions also accept--use-system-cato use the operating system’s store. - Python requests / pip: requests uses the
certifibundle, not the OS. SetREQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt(a bundle that includes your root); for pip usepip --cert /path/bundle.pemorPIP_CERT. Thetruststorepackage lets Python use the OS store instead. - Python on macOS from python.org: it has no certificates until you run Install Certificates.command in the
/Applications/Python 3.x/folder. This is the classic cause of the error withurllib. - git:
git config --global http.sslCAInfo /path/bundle.pem. On Windows,git config --global http.sslBackend schannelmakes git use the Windows certificate store, which already holds company roots. - curl:
curl --cacert /path/bundle.pem https://…orCURL_CA_BUNDLE. - Docker builds behind a proxy:
COPY corp-root.crt /usr/local/share/ca-certificates/thenRUN update-ca-certificatesbefore anything downloads.
Careful: some variables replace the default list instead of adding to it. REQUESTS_CA_BUNDLE pointing at a file with only your company root will break every other site; point it at the full system bundle after adding your root to the system store.
Why not just turn verification off?
curl -k, verify=False, NODE_TLS_REJECT_UNAUTHORIZED=0, npm config set strict-ssl false and git -c http.sslVerify=false all make the error disappear, and also make your program accept a fake certificate from anyone on the network. They tend to stay in scripts for years. Fix the trust store instead; it takes the same five minutes.
Look-alike errors
- certificate has expired: the chain is fine but a date is wrong; see certificate has expired.
- wrong version number: you are speaking HTTPS to a plain-HTTP port; see SSL wrong version number.
- hostname mismatch /
ERR_TLS_CERT_ALTNAME_INVALID: the certificate is valid but for a different name; check the URL, and remember a certificate forexample.comdoesn’t coverapi.example.comunless it lists it.
Only Python on a Mac fails while the browser is fine? See Python CERTIFICATE_VERIFY_FAILED on macOS.
Related
Cloudflare Error 525 · Cloudflare Error 526 · self-signed certificate in certificate chain · All guides
Questions
What does unable to get local issuer certificate mean?
Your program could not find the certificate authority that signed the server's certificate in its local trust store, so it cannot verify the chain. Usually a company proxy root, a missing CA package or a missing intermediate certificate is to blame.
Why does it work in my browser but not in Python or Node?
Browsers use the operating system's certificate store and can fetch missing intermediates. Python requests uses the certifi bundle and Node uses its own built-in list, so a company root added to your OS is not seen by them. Point them at it with REQUESTS_CA_BUNDLE or NODE_EXTRA_CA_CERTS.
Is it safe to set NODE_TLS_REJECT_UNAUTHORIZED=0 or verify=False?
No. It disables certificate checking entirely, so anyone between you and the server could impersonate it. Use it only for a quick local test, never in code you keep.
How do I fix it in a Docker container?
Install the CA package in the image (apt-get install ca-certificates or apk add ca-certificates). If you are behind a company proxy, also copy the company root certificate into /usr/local/share/ca-certificates and run update-ca-certificates.
Published .