Three kinds of certificate
| Certificate | Who holds it | Role |
|---|---|---|
| Leaf (end-entity) | Your server | Names your host(s) in its SANs. Signed by an intermediate. Typically valid for 90 days to a year. This is "the certificate" people mean. |
| Intermediate | The certificate authority | Signs leaf certificates. Signed in turn by a root (or another intermediate). Valid for a few years. Exists so the root's key can stay offline. |
| Root | The CA, and every client | Self-signed. Shipped in the operating system's or browser's trust store. Valid for decades. Never sent by the server, because the client already has it (or does not, in which case nothing you send will help). |
A client trusts your leaf by building a path from it to a root it already has: leaf, signed by intermediate, signed by root. Every link must verify. The server is responsible for sending the leaf and every intermediate; the client supplies the root.
The missing intermediate
The classic mistake is to install only the leaf. Your CA emailed you example.com.crt and ca-bundle.crt, and only the first one went into the server config. The server now sends a leaf signed by an intermediate the client has never seen, and the client cannot build a path to a root.
Then something odd happens: it works in Chrome. And Firefox. And Safari. So it ships. Browsers have two ways of recovering a missing intermediate: they cache intermediates they have seen on other sites (and Let's Encrypt's intermediates are on half the web), and most will follow the Authority Information Access URL in the leaf to fetch the intermediate on the fly. Firefox goes further and preloads all known intermediates.
Clients that do not do this: curl, wget, Python requests, Go's net/http, Java, .NET on Linux, OpenSSL's s_client, Android apps on some versions, smart TVs, payment terminals, printers, IoT firmware, monitoring agents, and the webhook sender at your payment provider. They see unable to get local issuer certificate and fail. The result is a site that works for every human who tests it and fails for every machine that matters.
How to check what your server sends
Do not trust a browser. Use a client that reports exactly what came over the wire. The SSL certificate checker lists every certificate the server presented, labelled leaf, intermediate or extra, and says whether the chain verified against a clean root store. If it shows one certificate and "Untrusted", you are sending only the leaf.
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null 2>/dev/null | grep -c 'BEGIN CERT' # 1 = leaf only (broken for many clients); 2 or 3 = leaf plus intermediates (correct) curl -vI https://example.com 2>&1 | grep -iE 'issuer|SSL certificate problem'
Check every endpoint separately. A load balancer with four backends, or a CDN with regional edges, can have the right chain on three and the wrong one on the fourth. Put the individual IP in the host field and the site name in SNI to test each one.
Other chain problems
Expired intermediate
Intermediates expire too, and when one does it takes every leaf signed by it with it. The leaf can be freshly renewed and still fail. The tool shows days left on each certificate in the chain, not just the leaf. If your CA has rotated intermediates, renewing the leaf usually brings the new one; make sure your deployment actually installs the new bundle rather than the leaf alone.
Wrong intermediate
Sending an intermediate that did not sign your leaf. Happens when a bundle from the previous CA is reused after switching providers. The path fails at the first link.
Sending the root
Harmless but wasteful. The client ignores it (it must use its own copy) and you have added a kilobyte or two to every handshake. Some scanners flag it. The tool labels it "extra".
Cross-signed roots and old devices
When a CA's new root is not yet in old trust stores, it publishes a cross-signed intermediate that chains to an older root as well. Which chain you serve decides which clients can connect. Let's Encrypt's 2021 expiry of the DST Root CA X3 cross-sign broke a great many older Android devices and every OpenSSL 1.0.2 client, even though the sites were doing nothing wrong. If you have a population of old clients, know which roots they have and serve the chain that reaches one of them.
Wrong certificate entirely
The chain is fine but the leaf is for a different name: the server's default virtual host, because SNI is misconfigured or the client did not send it. The tool reports this as a hostname mismatch and shows the SANs so you can see what it is for.
Configuring the chain
Every server wants the leaf first, then intermediates in order, in one PEM file or two. Certbot writes fullchain.pem for exactly this; use it, not cert.pem. Nginx: ssl_certificate fullchain.pem. Apache 2.4.8+: SSLCertificateFile fullchain.pem (the old SSLCertificateChainFile is deprecated). HAProxy: concatenate leaf, intermediates and key into one file. Cloud load balancers have a separate "certificate chain" field that is easy to leave empty; do not.
Then test with something that is not a browser.