An SSL certificate chain is the sequence of certificates that links the one installed on your server to a root certificate the visitor's device already trusts. Browsers do not trust your certificate directly; they trust it because each link in the chain is signed by the next, ending at a known certificate authority (CA). When a link is missing, the chain breaks, and some clients reject the connection even though your certificate itself is valid.
The Three Links#
| Certificate | Issued to | Signed by | Lives where |
|---|---|---|---|
| Leaf (end-entity) | Your domain | An intermediate CA | Your server |
| Intermediate | The CA's signing arm | The root CA (or another intermediate) | Your server, sent with the leaf |
| Root | The CA itself | Itself (self-signed) | The client's trust store |
Verification runs upward: the browser checks that your leaf was signed by the intermediate, the intermediate by the root, and that the root sits in its trust store. Any signature that fails, any expired link, or any missing intermediate breaks the whole chain.
Why Intermediates Exist#
CAs do not sign customer certificates with their root keys. A root key compromise would invalidate every certificate ever issued under it, so roots stay offline and delegate to intermediate certificates that do the day-to-day signing. The cost of that safety is on you: the server must send the intermediates along with the leaf, because clients only ship roots.
How the Chain Breaks#
The classic failure is the incomplete chain: the server sends only the leaf certificate. Desktop browsers often hide this because they cache intermediates from other sites or fetch missing ones; stricter clients do not. The symptom pattern is distinctive, and worth memorizing:
- Works in Chrome on your laptop
- Fails in
curlwithunable to get local issuer certificate - Fails on some mobile devices, smart TVs, or older Android versions
- Fails in server-to-server calls, webhooks, and monitoring agents
If your site "has SSL errors for some users but not others," check the chain first.
Inspecting a Chain#
openssl s_client -connect example.com:443 -servername example.com -showcerts
openssl s_client -connect example.com:443 -servername example.com -showcerts
The output lists every certificate the server actually sent. You want to see the leaf followed by each intermediate; Verify return code: 0 (ok) at the end means the chain validated. 21 (unable to verify the first certificate) is the incomplete-chain signature. Online checkers like SSL Labs report the same thing as "chain issues: incomplete."
Fixing It#
Serve the full chain, leaf first, then intermediates, in order:
# nginx: fullchain.pem = leaf + intermediates concatenatedssl_certificate /etc/ssl/fullchain.pem;ssl_certificate_key /etc/ssl/privkey.pem;
# nginx: fullchain.pem = leaf + intermediates concatenatedssl_certificate /etc/ssl/fullchain.pem;ssl_certificate_key /etc/ssl/privkey.pem;
Most ACME clients (Certbot, acme.sh) already produce a fullchain.pem; pointing your config at cert.pem instead of fullchain.pem is the most common way this breaks. Never include the root in the bundle; clients have it already, and sending it wastes a round trip of bytes on every handshake.
Common Pitfalls#
- Renewing the leaf but keeping a stale bundle. If the CA rotated intermediates, the old bundle no longer matches.
- Expired intermediates. The leaf can be valid while a middle link is not; the chain fails anyway.
- Wrong order. Some strict clients require leaf first, then ascending order.
- Cross-signed root expiry. Older devices that depend on an expired cross-sign (the 2021 Let's Encrypt DST Root X3 expiry was the famous case) fail while modern ones work.
Conclusion#
A certificate chain is only as valid as its weakest link, and the failure mode is sneaky: your own browser says everything is fine while stricter clients refuse to connect. The fix is almost always serving the complete, current bundle.
Because a broken chain often fails only for some clients, it is exactly the kind of outage you will not see from your own desk. An ObserveOne uptime check makes the request from outside, with no cached intermediates to paper over the gap, so a chain problem fails the check instead of waiting for user complaints.