Short answer
"unable to get local issuer certificate" means the client could not build a complete chain of trust from the server's certificate up to a root it trusts. The missing piece is almost always the intermediate certificate that the server forgot to send. The fix is to install the fullchain certificate — leaf + intermediate(s) — on the server.
Why does it work in the browser but not in curl?
Browsers are forgiving: they cache intermediates from earlier visits, and many can even fetch a missing intermediate via the certificate's AIA field. curl, OpenSSL, Java and most server libraries do not do this — they require the server itself to send the full chain. That is why the error is often discovered only when a backend integration goes live, not during browser QA.
Confirm it: how many certificates does the server send?
openssl s_client -connect example.com:443 -servername example.com -showcerts 2>/dev/null \ | grep -c "BEGIN CERTIFICATE"
If you get 1, the server sends only the leaf certificate — the chain is missing. If you get 2 or more, intermediate(s) are included. You can also see it directly in Verify return code: — code 20 is exactly this error.
How to fix it on the server
Install the certificate your CA calls fullchain.pem or the chain bundle — not just cert.pem. Examples:
# nginx — use fullchain, not just the leaf ssl_certificate /etc/ssl/example.com/fullchain.pem; ssl_certificate_key /etc/ssl/example.com/privkey.pem; # Apache — modern versions chain automatically if the file contains the chain SSLCertificateFile /etc/ssl/example.com/fullchain.pem SSLCertificateKeyFile /etc/ssl/example.com/privkey.pem
With Let's Encrypt/certbot, fullchain.pem is already the correct file — the error typically appears when someone manually pointed at cert.pem instead. Reload the service afterwards and verify with the count above.
The other cause: a missing root in the client's trust store
Rarer, but real: if the server sends the full chain correctly but the client does not have that root in its trust store, you see the same error. This typically hits older systems that lack updated CA bundles, or internal CAs that were not distributed to clients. See also "works in browser, not in Java/.NET".
How to catch it before your customers do
A missing chain is invisible in a browser-based test — which is exactly why it slips through to production. CertControl validates the full chain from the outside on every endpoint, not just the leaf certificate, and flags any server that sends only a partial chain. So you find it at the next scan, not when a partner's integration fails.
Frequently asked questions
What is the difference between cert.pem and fullchain.pem?
cert.pem contains only your own (leaf) certificate. fullchain.pem contains the leaf plus the required intermediates. Always use fullchain on the server, or you risk this error.
Why doesn't the client just fetch the missing intermediate itself?
Some browsers can via the AIA field, but it is not guaranteed and is slow. curl, Java and most server libraries do not. Best practice is for the server to always send the full chain.
Does the server also need to send the root certificate?
No. The root must be in the client's trust store, not sent by the server. Sending the root is unnecessary (though harmless). It is the intermediate that must be included.
Verify return code 20 — what does it mean?
It is OpenSSL's code for exactly "unable to get local issuer certificate": the chain could not be completed up to a trusted root.