Why your certificate looks fine in your browser but a check says it's broken

You see the padlock. The site loads, no warning, everything normal. Then a monitoring service, a colleague on a different machine, a fresh customer, or a plain curl reports the same certificate as broken or untrusted. Nobody is lying. The usual explanation is an incomplete certificate chain — a missing link that your browser quietly works around and stricter clients don't. This is the "works for me" of TLS, and once you see the mechanism it stops being mysterious.

It's worth saying plainly: when your browser and a fresh external check disagree, the external check is usually the more honest one. Your browser has had time to collect crutches that a stranger's device hasn't. To see why, you need to know what the chain is and which piece tends to go missing.

The chain, and the link that goes missing

A public certificate is rarely signed directly by a root authority. Instead there's a short chain of trust. Your certificate — the leaf — is signed by an intermediate certificate, and the intermediate is signed by a root. A client trusts the root (it ships in the device's trust store), and follows the signatures down: root vouches for intermediate, intermediate vouches for your leaf, therefore the leaf is trusted. Roots stay locked away and sign rarely; intermediates do the day-to-day signing. That layer exists so a root key almost never has to be used, which is what keeps it safe.

Here's the division of labour that causes all the trouble: the server is responsible for sending the leaf and the intermediate(s); the client supplies the root from its own store. The root is never sent over the wire — every client already has it or it wouldn't be trusted in the first place. So when a chain breaks, it's almost never the leaf or the root. It's the intermediate in the middle, left out of what the server sends. The certificate itself is perfectly good. The server is simply handing over an incomplete set of links and assuming the other end can finish the job.

Why your browser forgives it and a fresh check doesn't

If the intermediate is missing, why does the site work for you at all? Because mainstream browsers carry two different safety nets that close the gap — and a clean, unprimed client carries neither.

Fetching the missing link on the fly. Every certificate carries an Authority Information Access extension, and inside it a "CA Issuers" URL that points to a copy of the issuing intermediate. A client that does AIA fetching notices the gap, downloads the intermediate from that URL, completes the chain itself, and shows you no error. Chrome on desktop (Windows, macOS, and Linux) does this, and Apple's platforms do it through the operating system's certificate machinery, so Safari does too. The practical result is that a user on one of those clients never learns the server is misconfigured — the browser silently cleans up after it.

Remembering intermediates it has already seen. Browsers also cache intermediates they encounter. Visit any other site that served that same intermediate correctly, and your browser keeps a copy; the next time it meets a server that forgot to send it, it fills the gap from memory. This is the real reason the same site can work in one browser and fail in another on the same machine — one has the intermediate cached, one doesn't. Firefox reaches the same outcome by yet another route: it deliberately does not fetch intermediates on demand, but it preloads a large published list of known intermediates in the background, from the shared CA database the industry maintains, so it can usually supply a missing one from that local cache. (Those preloaded copies fill the chain but aren't themselves treated as trusted — trust is still derived the normal way.)

Now the other side. Command-line tools like openssl and curl, most uptime monitors, and many programming-language HTTP libraries do none of this. They take the chain exactly as the server presents it. If the intermediate isn't there, verification fails — full stop. That's not a defect; it's why they tell you the truth about your configuration when your own browser won't. A brand-new client with an empty cache and no fetching habit — a monitoring server in a fresh container, a colleague's laptop that happens never to have visited a site using that intermediate — sees what your server actually sent, which is an incomplete chain.

Why desktop works but mobile or old devices fail

The same split shows up across devices, for two reasons that stack on top of each other.

First, the safety nets above aren't evenly distributed. Desktop browsers are the most likely to fetch a missing intermediate or to have one cached from heavy browsing; many mobile and embedded clients — apps using the system networking stack, smart TVs, payment terminals, API clients on a phone — are less likely to, and behaviour here genuinely varies by platform and has shifted over the years, so it's not safe to assume a phone behaves like your laptop. A chain that a desktop completes for itself can simply fail somewhere with fewer crutches.

Second, and more absolute: trust stores differ, and old ones are frozen. Apple, Microsoft, Mozilla, Android, and Chrome each maintain their own list of trusted roots, and those lists aren't identical. More importantly, a device only learns about new roots through updates. A phone or TV that stopped getting updates years ago has a trust store frozen in time. The clearest example: in September 2021, Let's Encrypt's older root, DST Root CA X3, expired. Up-to-date devices had long since trusted Let's Encrypt's own newer root, ISRG Root X1, and never noticed the change. But devices too old to have ever received ISRG Root X1 abruptly distrusted an enormous slice of the web — because a root store that last updated in, say, 2014 cannot trust a root that only reached most devices around 2016–2018, no matter how valid every certificate beneath it is. The certificate isn't broken; the device's idea of who to trust is simply out of date, and frozen there.

Why a serverless or edge check can't reliably tell

You might expect that the easy way to test a certificate from the outside is to have a server somewhere just fetch() the URL and see if it works. It isn't, and the reason is the same theme one layer down. A high-level fetch — the kind a serverless function or edge runtime gives you — is built to retrieve a resource, not to audit a handshake. It hands back a response or throws an error. It doesn't expose the certificate chain for inspection, and it doesn't tell you, in any structured way, why a handshake failed: expired, untrusted, wrong host, and incomplete chain all collapse into the same generic failure, if they surface at all.

This is first-hand for us. When we built this tool's certificate check, we wanted to run it at the edge, and we found we couldn't get a trustworthy broken-versus-valid verdict from the edge runtime's fetch — in our own testing it would complete requests in cases where a careful client should have objected, because that convenience layer simply isn't built to validate and report the way we needed. So the certificate inspection in this tool doesn't run on the edge at all. It runs on a small, separate service that opens a real TLS socket, performs a full verified handshake, and reads the chain and the failure reason directly. We're describing our own design here, not a universal rule about every platform — but the lesson generalises: to learn what a stranger's client sees, you need something doing a genuine low-level handshake, not a wrapper optimised for fetching pages.

How to actually confirm what's broken

The throughline of every section above is the same: the only check you can trust is a fresh handshake from a client with nothing cached and no gap-filling habit — something that sees the chain exactly as your server sends it, the way a first-time visitor's device would.

On the command line, that's openssl s_client -connect yourdomain.com:443 -servername yourdomain.com. It prints the chain the server actually presented and the verification result, with no browser stepping in to complete it. A short chain followed by a verify error — typically about being unable to get the local issuer certificate — is the signature of a missing intermediate. The fix, in almost every case, is to install the full chain (leaf plus intermediate) rather than the bare certificate; renewal tools produce a "fullchain" file for exactly this purpose. If you'd rather not touch a terminal, an external checker that performs the same fresh, uncached handshake from outside your network gives you the same answer in plain language — including which of the failures it is, which is the part a raw error message makes you decode.

That's what the check below does. It runs the handshake from off your network, with no cached intermediates to lean on, and reports the certificate the way an unprimed stranger would see it — not the forgiving version your own browser has quietly assembled.

So when your browser and a monitor disagree, don't average them. The browser is showing you a best case stitched together from fetches and caches that not every visitor shares; the fresh external check is showing you the floor — what someone arriving cold actually gets. Fix to the floor, and everyone above it is fine too. If the result comes back "untrusted," our guide to what each SSL state means walks through the exact fix.

Sources