Skip to content
ohsudo, Home
Security

"SSL certificate problem: unable to get local issuer certificate": fix the chain, not the check

Fix "unable to get local issuer certificate" in curl, git, pip, npm and Docker: find the broken link with openssl, then fix the server chain or add the missing CA.

Intermediate4 min readApplies to OpenSSL 3 · curl · Git · Python / pip · Node.js / npm · Docker · Ubuntu · Debian · RHEL / Rocky / AlmaLinux

by OhSudo ··

A command that talks HTTPS refuses the connection:

$ curl https://intranet.example.com
curl: (60) SSL certificate problem: unable to get local issuer certificate

The same problem, in other tools:

fatal: unable to access 'https://git.example.com/team/app.git/': SSL certificate problem: unable to get local issuer certificate
[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate # Python, pip
Error: unable to get local issuer certificate # Node.js, npm

To trust a site, the client needs a chain: the site’s certificate, signed by an intermediate certificate, signed by a root certificate the client already trusts (from its “CA store”). “Unable to get local issuer certificate” means the client couldn’t complete that chain. One link is missing — and there are only three places it can be missing from.

  1. intranet.example.comthe site's certificate, sent by the server
  2. Intermediate CAthe server should send it too
  3. Root CAmust already be in the client's CA store
The client follows the chain up to a root it already trusts. The error means one link is missing: an intermediate the server didn't send, or a root this client's CA store doesn't have.

TL;DR

Terminal window
# 1. Look at the chain the server sends
openssl s_client -connect intranet.example.com:443 -servername intranet.example.com -showcerts </dev/null
# 2a. The server sends an incomplete chain → fix the server (use the full chain)
# 2b. A company proxy re-signs HTTPS → add the company root CA to the system:
sudo cp company-root.crt /usr/local/share/ca-certificates/ && sudo update-ca-certificates # Debian/Ubuntu
sudo cp company-root.crt /etc/pki/ca-trust/source/anchors/ && sudo update-ca-trust # RHEL family

1. Look at the chain

Terminal window
openssl s_client -connect intranet.example.com:443 -servername intranet.example.com -showcerts </dev/null

At the top, under Certificate chain, every certificate the server sent has two lines: s: (subject — who it is) and i: (issuer — who signed it). At the bottom, the verdict:

Certificate chain
0 s:CN = intranet.example.com
i:C = US, O = Let's Encrypt, CN = R11
---
...
Verify return code: 21 (unable to verify the first certificate)

Verify return code: 0 (ok) means OpenSSL on this machine trusts the server, and the problem is in the specific tool (section 4). Anything else, read on.

2. The server sends an incomplete chain

Above, the server sent only its own certificate (0 s:), not the intermediate R11 that signed it. The client has no way to reach a trusted root. This is a server-side mistake, usually from configuring the certificate file instead of the full chain.

Browsers often still show the site as secure — they cache intermediates they’ve seen before, or download missing ones — which is why “it works in Chrome” proves nothing here. curl, Git, Python and Node.js don’t do that.

Fix it on the server. With Let’s Encrypt, use fullchain.pem, not cert.pem:

ssl_certificate /etc/letsencrypt/live/intranet.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/intranet.example.com/privkey.pem;

With a certificate bought elsewhere, put your certificate first and the intermediate(s) after it in one file: cat example.com.crt intermediate.crt > fullchain.crt. Check again with openssl s_client: you should now see 1 s: too, and Verify return code: 0 (ok).

3. A company proxy or a private CA

If the issuer (i:) of the top certificate is your company, your antivirus or a security product (Zscaler, Netskope, Fortinet, Palo Alto…) instead of a public CA, then something on your network opens HTTPS traffic to inspect it and re-signs it with its own root certificate. The same happens with internal sites signed by a company CA. Your machine doesn’t know that root yet.

Get the company root certificate (from IT, or export it from the browser, which usually has it already) in PEM format, and add it to the system’s CA store:

Terminal window
# Debian / Ubuntu — the file must end in .crt
sudo cp company-root.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates
# RHEL / Rocky / AlmaLinux
sudo cp company-root.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust

curl, wget, Git and most system tools use this store and work right away.

4. Tools with their own CA store

Some runtimes don’t use the system store — they bring their own list, so step 3 isn’t enough for them:

Tool Point it at the system store, or add the company root
Node.js / npm export NODE_EXTRA_CA_CERTS=/path/to/company-root.crt
Python requests export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt (RHEL: /etc/pki/tls/certs/ca-bundle.crt)
pip pip config set global.cert /etc/ssl/certs/ca-certificates.crt
Java sudo keytool -importcert -cacerts -alias company-root -file company-root.crt

In Docker images, the store is inside the image. Slim images often don’t even have ca-certificates. Add both in the Dockerfile:

RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && rm -rf /var/lib/apt/lists/*
COPY company-root.crt /usr/local/share/ca-certificates/
RUN update-ca-certificates

If even public sites like https://github.com fail, the store itself is missing or broken: sudo apt install --reinstall ca-certificates (or sudo dnf reinstall ca-certificates).

Warning

Don’t turn verification off. curl -k, git config http.sslVerify false, NODE_TLS_REJECT_UNAUTHORIZED=0 or pip’s --trusted-host make the error go away by accepting any certificate — including an attacker’s. They also tend to stay in scripts and config files for years. Fix the chain or the store instead.

Shell, we have a problem!

Similar, but different: certificate has expired or certificate is not yet valid — check the server’s certificate dates, and also your own clock (timedatectl); a machine with a wrong date rejects valid certificates. See certificate has expired. self-signed certificate / self-signed certificate in certificate chain — the chain ends in a root that isn’t in your store: same fix as section 3 if you trust it. no alternative certificate subject name matches target host name — the certificate is valid, but for a different name. For kubectl, see x509: certificate signed by unknown authority. To get a certificate in the first place, see Certbot failed to authenticate.

Found this useful? Share it.

Found a mistake? Let us know at contact@ohsudo.com.

Tools

Try it yourself

Articles

Related articles