Problem

You ran a command against an HTTPS endpoint — a health check, an API call, openssl s_client, a curl in a deploy script — and it died with some form of:

verify error:num=20:unable to get local issuer certificate

From openssl s_client the same thing shows up at the bottom as:

Verify return code: 20 (unable to get local issuer certificate)

and from curl it’s wrapped as:

curl: (60) SSL certificate problem: unable to get local issuer certificate

Same underlying cause, three different front ends. The word that trips everyone up is local: it does not mean “a problem on your local machine.” It means OpenSSL, looking locally at everything it has — the certificates the server sent plus the trust anchors it knows about — could not find the issuer certificate it needs to continue building the chain. That gap can be on the server’s side or yours, and the entire fix depends on which.

Symptoms

  • openssl verify, openssl s_client, curl, wget, a language runtime (Python requests, Node, Java, Go), or a monitoring probe reports error 20 / curl 60 against an HTTPS host.
  • The same URL often opens fine in a web browser with a valid padlock.
  • It may fail everywhere you try it, or only from one particular machine or container.

What it actually means

A TLS certificate is the bottom of a chain. Your leaf certificate (for your domain) is signed by an intermediate CA, which is signed — directly or through more intermediates — by a root. The client trusts the root because the root lives in its trust store. It does not carry your specific intermediate; there are hundreds and they rotate. So to validate your leaf, the client has to assemble an unbroken path from the leaf up to a root it already trusts, and it needs every link except the root to do that.

Error 20 is OpenSSL saying: I got to a certificate, I need its issuer to keep going, and that issuer is nowhere I can see. The chain is broken somewhere between the leaf you presented and a root the client trusts. The only real question left is which end dropped the link.

(If you got the sibling message error 21, “unable to verify the first certificate,” the gap is at the very start — only the leaf was presented, with no issuer at all. Error 2, “unable to get issuer certificate,” is the closely related “the trusted-certificate list isn’t complete” case. OpenSSL’s own docs have historically muddled the fine wording of 2 versus 20, so don’t over-read the distinction — all three mean the same practical thing: the path to a trusted root isn’t complete.)

The split: server sent too little vs. your box trusts too little

There are two causes, and one command tells them apart.

The server sent an incomplete chain (server-side). The most common cause by far. The server is configured with only its leaf certificate and omits the intermediate — in nginx, this is pointing ssl_certificate at the bare cert.pem instead of fullchain.pem. The server hands the client a leaf signed by an authority the client was never given, the client can’t bridge the gap, and you get error 20. This server is misconfigured, full stop — it only looks fine because browsers quietly fetch the missing intermediate over AIA and cover for it. Every strict client fails identically, from every machine.

Your trust store is missing or stale (client-side). OpenSSL ships with no built-in trust anchors of its own — it relies on the operating system’s CA bundle, which the distro installs and points OpenSSL at. If that bundle is missing, empty, outdated, or you’re inside a minimal container (Alpine, scratch, a slim base image) that never installed ca-certificates, then even a correctly-chained, perfectly valid public certificate fails with error 20. The tell here is that it fails from this environment while the same host validates fine from a normal desktop. A corporate TLS-intercepting proxy produces the same thing: it re-signs traffic with an internal root your machine doesn’t trust.

The one command that decides it

openssl s_client -connect example.com:443 -showcerts

-showcerts prints exactly the certificates the server sent, in the order it sent them — and the docs are explicit that this is not a verified chain, just what came down the wire. Count them:

  • Only one certificate (just the leaf)? The server is sending an incomplete chain. It’s server-side. Fix the server to serve leaf + intermediate(s).
  • A full chain (leaf, then one or more intermediates) and you still get Verify return code: 20? The certs are fine; your trust store is the problem. It’s client-side. Fix or point to the CA.

That single distinction saves you from “fixing” the server when the fault is your container, or reinstalling CA bundles when the server was never sending its intermediate.

Diagnose with DechoNet

  • SSL Check performs a real TLS handshake from the outside and reports the certificate chain the host actually serves — so you can confirm, independent of your own machine’s trust store, whether the server is presenting its intermediate(s) or stopping at the leaf. If the check shows a complete, valid chain but your local tool still returns error 20, the problem is on your side; if the chain is incomplete, it’s the server’s.
  • HTTP Check confirms whether the HTTPS endpoint completes a request at all from an independent vantage point, separating “the whole TLS setup is broken for everyone” from “only my environment rejects it.”

Resolution Checklist

  • Run openssl s_client -connect host:443 -showcerts and count the certificates the server sent.
  • Server-side (only the leaf came back): reconfigure the server to serve the full chain — in nginx, ssl_certificate must point at fullchain.pem (leaf + intermediates), not cert.pem. Reload and re-test.
  • Client-side (full chain came back, still error 20): install or update the system CA bundle (ca-certificates), especially in minimal containers, or pass -CAfile chain.pem / -CApath to point OpenSSL at a trust store.
  • If you’re behind a corporate TLS-intercepting proxy, add its internal root to your trust store rather than disabling verification.
  • Verify a fix with openssl verify -untrusted intermediate.pem -CAfile root.pem leaf.pem, or re-run s_client and confirm Verify return code: 0 (ok).
  • Do not paper over it with -k / --insecure or NODE_TLS_REJECT_UNAUTHORIZED=0; that disables verification without supplying the missing issuer.

When to Escalate

  • If the server is serving an incomplete chain but you don’t control it — a third-party API, a vendor endpoint — the fix is theirs. Send them the s_client -showcerts output showing only the leaf; it’s unambiguous evidence.
  • If the chain is complete and your trust store is current and it still fails from one environment only, capture openssl s_client -connect host:443 -showcerts -CAfile <bundle> and compare trust stores between the working and failing machines to find the missing or extra root.
  • If the underlying issue is a genuinely untrusted or self-signed issuer rather than a missing-link chain, you’re in a different error family — see NET::ERR_CERT_AUTHORITY_INVALID and curl: (60) SSL certificate problem for the trust-anchor-vs-chain split.

Check your own domain now

Free, no sign-up. Runs the exact check this guide describes and shows what to fix.