Writing

SSL certificate problem: unable to get local issuer certificate

Published 14 September 2026

This error has two opposite causes. One is fixed on the server and affects everybody; the other is fixed on your machine and affects only you. Almost every page about it treats them as the same problem, which is why people end up reinstalling CA bundles to fix a server they should have reconfigured.

Work out which one you have first. Paste the hostname into the SSL checker. It reads the chain exactly as the server sends it, so it can tell you whether the server is at fault, which is the question that decides everything below.

What the error actually means

A TLS certificate is not trusted on its own. It is trusted because it was signed by an intermediate certificate, which was signed by a root certificate that your machine already trusts. That sequence is the chain, and a client has to be able to build the whole thing.

The error means the client got stuck: it had a certificate, and it could not find the certificate that signed it. That is all it means. It does not say whose fault it is.

The same error, in whatever you are using

These are all the same failure:

Notably, the one place you will probably not see it is a browser. That is a clue, not a reassurance, and it is the subject of the next section.

Which cause do you have?

Cause 1: the server is not sending its intermediate

A server is supposed to send its own certificate and the intermediate that issued it. Plenty send only the leaf. When that happens, browsers usually paper over it: they have often cached the intermediate from another site, and they will quietly download it using the Authority Information Access extension inside the certificate itself. The padlock appears and nothing looks wrong.

curl, openssl, Java, Go, Python, mobile SDKs and most API clients do neither. They fail. This is why "it works in my browser" is not evidence that the server is fine: it is the single most misleading signal in this whole situation.

Tell-tale signs: it fails on more than one machine, it fails in CI but works on a laptop, or it works in a browser and fails in every tool.

Cause 2: your machine's trust store is missing or stale

The root certificate at the top of the chain has to be installed locally. On a minimal container image it often is not: alpine famously ships without ca-certificates. On an old machine the bundle may predate the CA that issued the certificate.

Tell-tale signs: it fails on one machine and works everywhere else, it started after switching base images, or every HTTPS site fails rather than one.

Cause 3: something is intercepting TLS

Corporate proxies, some antivirus products and many VPN clients decrypt HTTPS and re-sign it with their own CA. That CA is installed in the operating system trust store (so browsers accept it) but tools with their own bundles, like Python's certifi or Node, never see it.

Tell-tale signs: only on the office network, and the issuer of the certificate you receive is your employer rather than a public CA.

How to tell them apart

The fastest way is the SSL checker: it runs from outside your network and reports the chain as sent, so it removes your machine and your proxy from the question entirely. If it reports an incomplete chain, it is cause 1 and the server needs fixing.

By hand, with openssl:

openssl s_client -connect example.com:443 -servername example.com

Look at the last line of the output:

Those two codes are one digit apart and mean opposite things. It is worth reading carefully.

Fixing cause 1: the server

Your CA gave you at least two files: the certificate and one or more intermediates. Most also provide them pre-joined, usually called fullchain.pem or a "bundle". The server must serve the joined version, leaf first.

nginx

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

Using cert.pem instead of fullchain.pem is the single most common way to cause this error. nginx has no separate directive for the chain: it must be concatenated into the one file.

Apache

SSLCertificateFile    /etc/letsencrypt/live/example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem

SSLCertificateChainFile still works but has been deprecated since Apache 2.4.8, which accepts the chain in the main certificate file.

HAProxy

bind :443 ssl crt /etc/haproxy/certs/example.com.pem

That single file must contain the leaf, then the intermediates, then the private key, in that order.

Order matters

Leaf first, then each issuer in turn. Some clients tolerate the wrong order; strict ones reject it, which produces a failure that appears inconsistent across your estate for no visible reason.

cat leaf.crt intermediate.crt > fullchain.pem

Fixing cause 2: your trust store

# Debian / Ubuntu
sudo apt-get update && sudo apt-get install --reinstall ca-certificates

# Alpine: the usual culprit in containers
apk add --no-cache ca-certificates

# RHEL / Fedora
sudo dnf reinstall ca-certificates && sudo update-ca-trust

Runtimes that carry their own bundle need telling separately:

# Python: uses certifi, not the system store
pip install --upgrade certifi

# Node: add a CA without disabling verification
export NODE_EXTRA_CA_CERTS=/path/to/ca.pem

# Java: its own keystore entirely
keytool -importcert -alias corp-ca -file ca.crt \
  -keystore "$JAVA_HOME/lib/security/cacerts"

For cause 3, the certificate to install is your organisation's interception CA, which your IT team can provide. It is the same mechanism: you are choosing to trust them.

What not to do

Do not turn verification off. These all "fix" the error by no longer checking anything:

curl -k                              # --insecure
export NODE_TLS_REJECT_UNAUTHORIZED=0
requests.get(url, verify=False)

You have not solved the problem, you have removed the protection that noticed it. An attacker who can position themselves between you and the server can now present any certificate at all and you will accept it. If it is cause 1, you are also leaving the server broken for everyone else who is stricter than you.

There is one defensible use: a single throwaway command to confirm the diagnosis. Never in code that ships, never in a container image, never as an environment variable somebody will forget.

Check the fix

After changing the server, confirm it from outside: not from the machine you changed, which may have the intermediate cached.

openssl s_client -connect example.com:443 -servername example.com | head -20

You want to see more than one certificate listed in the chain, and Verify return code: 0 (ok).

Or paste the hostname into the SSL checker, which will tell you how many certificates the server actually sent and whether the path is now complete. It deliberately does not repair the chain before reporting on it, so it cannot tell you something is fine when it is only fine for browsers.

Why this keeps happening

Because the feedback is wrong. The person who configures the server checks it in a browser, sees a padlock, and moves on. The failure surfaces days later in somebody else's CI pipeline, or on a mobile client, or on the one Linux box that never visited the CA's other customers. By then it does not look like a server problem at all.

The habit worth forming is to verify with something that does not repair the chain for you.

If the failure happens before any certificate is mentioned: the connection simply refused: the cause may be elsewhere in the handshake entirely: peer failed to perform TLS handshake works through the six things that produce it.

If a browser is the thing complaining rather than a command-line tool, the wording will be NET::ERR_CERT_AUTHORITY_INVALID, the same underlying problem with local HTTPS interception as a much more likely cause.

The same chain rules apply when the service is reached by address instead of name. IP address SSL certificates are now publicly available for validated public IPv4 and IPv6 addresses; the address must appear as an IP entry in the SAN, and the server still has to send the complete intermediate chain.