
Somebody tells you your site is insecure. You open it: padlock, no warning, certificate valid for another four months. You ask them to try again and it still fails, on a network you cannot reach, in a client you do not have.
Neither of you is wrong, and the certificate is not the problem. What differs is everything underneath it.
A certificate is not trusted on its own
Your certificate does not assert that it is trustworthy. It is signed by an intermediate certificate, which is signed by a root, and only the root is in the visitor’s trust store. The job at connection time is to build that path, and the job of your server is to hand over the middle of it.
TLS 1.3 says what the server sends, and the wording repays reading:
certificate_list: A sequence (chain) of CertificateEntry structures, each containing a single certificate and set of extensions.
And then the rule that is usually skipped:
The sender’s certificate MUST come in the first CertificateEntry in the list. Each following certificate SHOULD directly certify the one immediately preceding it. Because certificate validation requires that trust anchors be distributed independently, a certificate that specifies a trust anchor MAY be omitted from the chain, provided that supported peers are known to possess any omitted certificates.
Read the last sentence carefully. The trust anchor, meaning the root, may be left out, because clients already have it. Nothing there excuses leaving out the intermediate. Your server is supposed to send everything between your certificate and the root, and an intermediate is precisely that.
The misconfiguration you will never see yourself
Leaving out the intermediate does not produce a half-broken site. It produces a site that works perfectly for you and fails for other people, which is a considerably more expensive outcome.
Mozilla has described not specifying proper intermediate CA certificates as “one of the most common server configuration problems”, and did something about it: Firefox preloads intermediate CA certificates in advance, in a list that “contains over two thousand entries”, specifically to reduce “unknown issuers errors in the TLS Handshake”.
Sit with what that means. Browsers actively compensate for this mistake. A browser that already holds the intermediate will complete the connection whether or not your server sent it, so your own test passes and the misconfiguration stays invisible. The compensation is good engineering and it is also the reason nobody notices.
Why one client fails where another succeeds
There is a second mechanism that papers over it, and it comes from the certificate itself.
A certificate can name where its issuer’s certificate may be found. RFC 5280 defines that field, and is careful about what it is for:
the id-ad-caIssuers OID is used when the additional information lists certificates that were issued to the CA that issued the certificate containing this extension. The referenced CA issuers description is intended to aid certificate users in the selection of a certification path
“Intended to aid” is the operative phrase. It is a pointer, not an obligation. A client may follow it and fetch the missing piece, and a client may decline. Both are conforming.
The TLS specification is similarly candid about the state of the world, noting that servers sometimes send extra or misordered certificates and that “others are simply configured incorrectly”, before asking implementations to be tolerant of it.
- What you tested with
- A current desktop browser, which preloads thousands of intermediates and will fetch a missing issuer if it has to. The most forgiving client you own
- What your visitor used
- Something stricter or older, which validates only what it was handed and reports an unknown issuer when the chain does not reach a root
So tolerance is a client decision rather than a property of your configuration, and you have been testing with the client most likely to forgive you.
The reports this actually generates
Because the forgiving clients are the browsers, the failures land somewhere else, and they rarely arrive described as a certificate problem.
- A webhook stops being delivered. Somebody else’s server is the client now, and it has no preloaded list and no interest in fetching your issuer.
- A mobile app cannot sign in while the same account works on the web, because the app’s networking stack is not the system browser.
- A payment or shipping integration fails against your callback URL, usually with a message about an unknown or untrusted issuer.
- One office or one older device cannot reach the site at all, and everybody else can.
None of those sound like “the certificate chain is incomplete”, which is why the hours go into the wrong place first.
The intermediate is the unit of blast radius
One more reason to know which intermediate you depend on, separate from whether you send it.
An intermediate signs a great many certificates. If one is withdrawn, distrusted or allowed to expire, every certificate under it stops validating at the same moment. Your certificate can be months from its own expiry, unchanged, and still fail, because the thing that broke is one link above it and was never yours.
Most certificate monitoring watches a single date: when your own certificate expires. That is the right date to watch and it is not the only one, and the other one belongs to a certificate you did not choose and probably cannot name.
Checking it takes about thirty seconds
Ask the server what it actually sends, from something that is not your browser:
openssl s_client -showcerts -connect example.com:443 -servername example.com </dev/null
Two things to read in the output. Count the certificates: a typical public chain hands over your
certificate and at least one intermediate, so exactly one certificate is the classic symptom of
this fault. Then read the issuer of your own certificate and check that a certificate with that
subject is present further down.
Session Replay
Free Chrome extension. One click on the page that is misbehaving captures the screenshot, the console and the network log, and hands you a link to paste into the ticket.
If what reaches you is a screenshot of a warning screen instead, the five different faults behind that one page are worth separating before you start: your connection is not private.
What to take from it
A certificate is a link, not a credential. It is trusted because a path can be built from it to something the visitor already trusts, and your server is responsible for the middle of that path.
The failure mode is unusually cruel because the compensation is so good. Browsers preload thousands of intermediates and will chase a missing issuer, so the client you test with is the client least likely to tell you. The ones that do tell you are webhooks, mobile apps and payment integrations, and they tell you in words that never mention certificates.
The specifications are RFC 8446 for what a server must send and RFC 5280 for the field that names an issuer’s location.