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.2or 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.
Other chain-related errors
- 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 mismatchin 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.