How to Check an SSL Certificate Chain (and Why Missing Intermediates Silently Break Things)
Your site works in Chrome. Safari shows the padlock. Then your iOS app fails to connect, your CI build cannot reach your API, and a customer's cURL call errors out with unable to get local issuer certificate. Same server, same cert, same everything. The silent culprit is almost always a broken certificate chain.
What "The Chain" Actually Means
An SSL certificate is trusted because it was signed by another certificate, which was signed by another, up to a root certificate that browsers and operating systems trust by default. This signed-by path is the chain. A typical chain looks like this:
Your leaf cert (example.com)
↑ signed by
Intermediate (e.g., Let’s Encrypt R3)
↑ signed by
Root CA (e.g., ISRG Root X1) ← preinstalled in browsers/OSes
Your server must serve the leaf and every intermediate. It must not serve the root (clients already have it). If even one intermediate is missing, the client cannot build the chain up to a trusted root, and the connection fails.
Why the Bug Is So Silent
Browsers cheat. Modern Chrome, Firefox, and Safari maintain a cache of intermediates they have seen on other sites. If your server forgets to send an intermediate, the browser likely already has it cached and completes the chain on its own. You see the padlock. You think everything is fine.
Every other client is less forgiving. cURL, wget, Python's requests, Go's net/http, Java's HttpsURLConnection, Android's TrustManager, mobile apps, webhook receivers, monitoring agents — they trust only what is served in the handshake. No intermediate cache. If it is missing, they fail.
Symptom pattern: works in the browser, broken in every programmatic client. If you see that combination, the chain is wrong 9 times out of 10.
Check the Chain With OpenSSL
One-liner to dump everything the server sends:
openssl s_client -connect example.com:443 -servername example.com -showcerts < /dev/null
Look for the Certificate chain section. You should see two or more certificates listed — leaf (depth 0) plus intermediates. If there is only one, your chain is broken.
Also check the verify result at the bottom:
Verify return code: 0 (ok) ← good
Verify return code: 21 (unable to verify ...) ← broken chain
Check the Chain With cURL
cURL uses its own CA bundle and does not cache intermediates, so if curl cannot connect, a lot of other programmatic clients cannot either.
curl -v https://example.com 2>&1 | grep -E "SSL|cert|verify"
If you see SSL certificate verify ok, chain is fine. If you see unable to get local issuer certificate, it is broken.
Or Check It in Your Browser — No Commands
Paste a domain into our Certificate Chain Checker and it will walk every cert in the chain, verify signatures up to a trusted root, and flag missing intermediates. It also checks CRL status (revoked certs in the chain) and expiration for each cert.
How Chains Actually Break
- Nginx or Apache config points to the leaf file instead of the fullchain file. Let's Encrypt produces both
cert.pem(leaf only) andfullchain.pem(leaf + intermediates). You want fullchain. - Cert renewal did not re-download the intermediate. Some tools refresh the leaf but skip the intermediate bundle. Every time the CA rotates intermediates, you break until you re-fetch.
- Copy-paste from a browser Export Certificate dialog. Browsers typically only export the leaf. You lose the intermediates in the process.
- Load balancer terminating TLS serves the wrong chain. AWS ALB, Cloudflare custom cert, GCP LB — every one of these has its own place to upload the intermediate bundle. Forgetting to upload it there is a classic "works locally, broken behind the LB" situation.
The Fix
Replace the certificate file your server points to with a concatenated leaf plus intermediate(s):
cat leaf.crt intermediate.crt > fullchain.crt
Order matters: leaf first, then intermediate(s), never the root. Reload your web server. Re-check with curl -v and you should see verify ok.
Run a quick validation against any domain with our chain checker. It verifies signatures, shows every cert in the chain with its expiration, and flags the exact intermediate that is missing if one is.