Your site shows a padlock in Chrome on your laptop — but curl, a mobile app, an API client or an older Android phone reports “unable to get local issuer certificate”, “certificate verify failed” or “incomplete chain”. The usual cause: the server sends only your certificate, not the intermediate certificate(s) that link it to a trusted root.

How the chain works

Root CA (in the client's trust store)
  └── Intermediate CA  (must be sent by your server)
        └── Your certificate (example.com)

Clients trust a small set of root certificates. CAs sign customer certificates with intermediates, not roots. Your server must send its certificate plus the intermediates, so the client can build a path to a root it trusts.

Desktop browsers often hide a missing intermediate because they’ve cached it from another site or fetch it automatically (AIA fetching). Many other clients — curl, Python, Java, Go, mobile apps, payment gateways, webhooks — don’t. That’s why the problem appears “only sometimes”.

Diagnose

openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | grep -E 'depth|verify|Certificate chain' -A2

Or look at how many certificates the server sends:

openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null 2>/dev/null | grep -c 'BEGIN CERTIFICATE'
  • 1 → only the leaf; intermediates are missing.
  • 2 or more → chain present (the root itself shouldn’t be sent; it’s harmless but wasteful).

Look for Verify return code: 0 (ok) versus 21 (unable to verify the first certificate). Online tools (SSL Labs, whatsmychaincert-style checkers) show “Chain issues: Incomplete”.

Fix for Let’s Encrypt / Certbot

Use fullchain.pem, not cert.pem:

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

cert.pem contains only your certificate; fullchain.pem contains your certificate followed by the intermediate. This mix-up is the single most common cause.

Fix for certificates from a commercial CA

The CA provides your certificate and a “CA bundle” / “intermediate” file. Build the chain in the right order — your certificate first, then intermediates:

cat example_com.crt intermediate.crt > /etc/ssl/example.com/fullchain.pem

If the bundle contains several intermediates, include them in order from the one that signed your certificate upward. Don’t include the root.

Nginx

ssl_certificate     /etc/ssl/example.com/fullchain.pem;
ssl_certificate_key /etc/ssl/example.com/example.com.key;

Apache 2.4.8+

SSLCertificateFile    /etc/ssl/example.com/fullchain.pem
SSLCertificateKeyFile /etc/ssl/example.com/example.com.key

(Older Apache versions used a separate SSLCertificateChainFile.)

HAProxy

One PEM containing certificate, intermediates and private key:

cat fullchain.pem privkey.pem > /etc/haproxy/certs/example.com.pem

Windows / IIS

Import the intermediate into the “Intermediate Certification Authorities” store of the local computer, or import a PFX that contains the full chain.

Reload the server and re-test.

  • Wrong order — leaf not first, or intermediates reversed. Some clients fail; fix the file order.
  • Wrong intermediate — the CA reissued with a different intermediate; download the current bundle.
  • Key mismatch — key values mismatch in Nginx. Compare public key hashes — see OpenSSL commands.
  • Expired intermediate or root on old clients — very old devices with outdated trust stores may not trust newer roots; usually the fix is updating the client, not the server.
  • Self-signed or private CA — clients must be given your CA certificate explicitly. See private CA.

Verify after fixing

curl -sI https://example.com | head -1
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | grep 'Verify return code'

Then set up expiry monitoring so you’re not surprised later — see check SSL certificate expiry.

  • SSL
  • TLS
  • Troubleshooting