Fix an incomplete certificate chain (works in Chrome, fails everywhere else)
This is one of my favorite bugs to diagnose, because the symptom is so misleading. Your site loads perfectly in Chrome. Padlock's there, no warning, looks great. Then your mobile app throws an SSL error, a partner's API call fails, your payment webhook stops firing, and someone on an Android phone says the site won't open. Same certificate, wildly different results. The culprit is almost always an incomplete certificate chain — a missing intermediate. Here's what's actually going on.
What a certificate chain is
When a browser connects to your site over HTTPS, it doesn't just trust your certificate because it exists. It follows a chain of signatures up to something it already trusts:
Your certificate (leaf)
└─ signed by → Intermediate certificate
└─ signed by → Root certificate (in the client's trust store)
The root lives in the client's trust store — baked into the operating system or browser. You don't send it, and you can't; the client already has it. The intermediate sits in between, and this is the one that matters: your server has to hand it over during the TLS handshake, because the client almost certainly doesn't have it. Your job is to send the leaf plus every intermediate so the client can draw an unbroken line from your certificate to a root it trusts.
Miss the intermediate, and the chain has a gap. The client is holding your leaf and a root, with nothing connecting them.
Why Chrome hides the problem
Here's the maddening part — and the reason this bug ships to production. A missing intermediate doesn't fail everywhere. It fails selectively, and the one client you test in is usually the one that forgives it:
- Desktop Chrome and most modern browsers paper over the gap. They cache intermediates they've seen on other sites, and they'll fetch a missing one on the fly from the
AIA(Authority Information Access) URL embedded in your certificate. So it just works — for you. - Everything else, often, does not.
curl, OpenSSL, most programming-language HTTP clients (Java, Python, Go, Node depending on config), older Android versions, payment gateways, and webhook senders generally do not go fetch a missing intermediate. They see a broken chain and refuse the connection.
So the classic signature of this bug: it's green in your browser and red everywhere a machine talks to you. The server-to-server traffic you can't see in a browser — APIs, webhooks, mobile apps — is exactly where it bites.
The giveaway error from curl is SSL certificate problem: unable to get local issuer certificate. If you've seen that against a site that looks fine in Chrome, you've found an incomplete chain.
Confirm it in one command
Don't guess — look at what your server actually sends. From any machine with OpenSSL:
openssl s_client -connect yourdomain.com:443 -showcerts
Read the certificate list it prints back. You want to see your leaf certificate and at least one intermediate (look for the s: subject and i: issuer lines — a proper chain shows each certificate's issuer matching the next certificate's subject). If you only see one certificate, that's your problem: the server is sending the leaf alone. You can also read the Verify return code at the bottom — anything other than 0 (ok) is a chain or trust issue.
The fix (usually two minutes)
You're not reissuing anything. You just need the server to send the intermediate too.
- Let's Encrypt / Certbot: point your server config at
fullchain.pem, notcert.pem. That single swap is the entire fix in the large majority of cases —fullchain.pemis your leaf plus the intermediate, which is exactly what belongs on the wire. This is the most common cause of incomplete chains I see: someone wired upcert.pemby mistake. - Other CAs: concatenate your certificate and the CA-provided intermediate bundle into one file, leaf first, then intermediates, and point your server at that. Order matters.
- A load balancer or CDN in front of your origin has its own certificate config — fix it there, not (only) on the origin.
Reload the server, run the openssl command again, and confirm the intermediate now shows up and the verify code is 0. Then test with curl from outside to be sure the machines are happy, not just the browsers.
Why this keeps happening
Certificate chains break on renewal. A cert gets reissued, a config points at the wrong file, a CA rotates its intermediate, a server migration copies over cert.pem instead of fullchain.pem — and because it still looks fine in Chrome, nobody notices until a machine complains. With certificates now renewing more often and lifespans shrinking toward 47 days, there are simply more renewal events where this can go wrong. It's the same lesson as a silently expiring certificate: the browser you happen to test in is the least reliable place to judge your TLS health.
Check from the outside, with a tool that doesn't forgive a missing intermediate, and check on a schedule — not just the day you set it up.
Catch chain problems before your APIs do
Perimeter validates your full certificate chain the strict way — the way curl, Android and payment gateways do, not the forgiving way Chrome does — alongside your headers, DNS and accessibility. And it re-checks on a schedule, so a renewal that quietly drops the intermediate gets flagged before a webhook or mobile app fails. Free, no signup.
Monitor your certificates → · Agency plan $29/mo, up to 25 domains