There's a particular class of TLS incident that drives me up the wall: the certificate that works perfectly in every browser on every laptop in the office, passes a quick visual check from anyone who looks at it, and then silently breaks every non-browser client in your infrastructure. Curl returns "SSL certificate problem: unable to get local issuer certificate." Python's requests library throws an SSLError. Your partner's webhook receiver starts dropping payloads. And your monitoring didn't catch any of it because your monitoring also uses a browser.
The root cause is almost always an incomplete certificate chain. The reason it's so hard to catch is that the most popular browser in the world has been quietly papering over this misconfiguration for years.
The asymmetry nobody talks about enough
When Chrome connects to an HTTPS endpoint and receives a leaf certificate without the necessary intermediate certificates, it doesn't just fail. It reads the Authority Information Access extension embedded in the certificate, finds a URL pointing to the missing intermediate, fetches it over HTTP, builds the chain itself, and completes the handshake. The user sees a padlock. Everything looks fine.
This is called AIA fetching, and Chrome has done it for as long as most engineers can remember. Firefox supports it now too, though its implementation details differ slightly. Safari handles it on macOS and iOS with its own quirks.
Almost nothing else does.
Curl doesn't fetch missing intermediates. It validates the chain it receives against its local trust store, and if the chain is incomplete, the connection fails. Python's requests library—urllib3 and OpenSSL under the hood—behaves the same way. Java's HttpClient validates against the JVM's cacerts trust store and will not go hunting for intermediates you forgot to serve. Go's crypto/tls won't either. Neither will most embedded devices, IoT clients, older Android versions, or the server-side HTTP client your payment processor uses to send you webhooks.
So every human who checks the site says it's fine. Every machine-to-machine integration is broken. I've watched this exact scenario eat hours of debugging time because the people investigating kept opening the URL in their browser, seeing the padlock, and concluding the certificate was valid.
The configurations that break things
A handful of chain misconfigurations account for the vast majority of these incidents. They're all variations on the same theme—the server isn't sending what the client needs to build a trust path—but the specifics matter for diagnosis.
Missing intermediate certificate
The most common case by far. The server sends the leaf certificate but omits one or more intermediate certificates that sit between the leaf and the root. The root is in the client's trust store, but the client has no way to get from the leaf to the root without the intermediates. Browsers fetch the missing piece via AIA. Everything else fails.
This happens most often during certificate renewal. Someone exports the new leaf cert from their CA's portal, copies it to the server, and doesn't realize the intermediate has changed—or doesn't realize it needs to be concatenated into the certificate file at all. If you've worked with Nginx, you know the drill: the ssl_certificate directive needs to point to a file containing the leaf followed by the full intermediate chain. Miss that step, and browsers still work. Curl doesn't.
Wrong certificate order
TLS requires the certificate chain be sent in order: leaf first, then the intermediate that signed the leaf, then the intermediate that signed that one, all the way up. Some servers send them jumbled. Some TLS implementations are forgiving about this. Others aren't. OpenSSL has historically been lenient with ordering, but not all versions behave identically, and other libraries may reject a misordered chain outright.
Expired intermediate
This one catches people off guard. You renewed your leaf certificate—valid for another year—but the intermediate your server is still bundling expired three months ago. The leaf is technically valid (it was signed while the intermediate was active), but clients validating the full chain will reject the expired intermediate. Browsers may work around this if they have the newer intermediate cached or can fetch it via AIA. API clients won't.
Cross-sign confusion and the Let's Encrypt lesson
September 2021. The DST Root CA X3 expiry taught the entire internet this lesson at scale.
Let's Encrypt had been cross-signed by DST Root CA X3 to maintain compatibility with older devices that didn't have ISRG Root X1 in their trust stores. When DST Root CA X3 expired, any client that followed the chain through the expired cross-sign—instead of the direct ISRG Root X1 path—started failing. Older versions of OpenSSL were particularly affected because of how they handled chain building.
This wasn't hypothetical. Old Android phones, embedded systems, enterprise Java applications running older JDK versions—millions of devices started throwing certificate errors. The fix varied depending on the client, the OpenSSL version, and the trust store configuration. It was a mess. And it showed exactly how fragile certificate chain validation becomes when you're depending on a specific path through a web of cross-signatures.
How to actually diagnose incomplete chains
If you suspect a chain issue, the worst thing you can do is open the URL in Chrome. Chrome will lie to you. Or more precisely, it'll show you what the experience looks like for a client that does AIA fetching, which is not the client you're troubleshooting.
Use openssl s_client
The most reliable diagnostic tool is openssl s_client. It shows you exactly what the server sends during the TLS handshake. No AIA fetching, no trust store magic—just the raw chain.
When you connect, look at the certificate chain output. It lists each certificate in order with its subject and issuer. A healthy chain starts with your leaf certificate (depth 0), followed by each intermediate, ending at or near the root. If you see only depth 0—just the leaf—the server isn't sending intermediates. If the issuer of one certificate doesn't match the subject of the next, you have an ordering problem. If any certificate in the chain shows a notAfter date in the past, you're serving an expired intermediate.
Pay attention to the "verify return code" at the bottom. Code 0 means the chain validated successfully. Code 21 means "unable to verify the first certificate"—almost always a missing intermediate. Code 20, "unable to get local issuer certificate," can mean the same thing depending on your local trust store.
Read the AIA extension
You can also examine the AIA extension in the leaf certificate using openssl x509. The "CA Issuers" field contains the URL where the intermediate can be fetched. If your server isn't bundling the intermediate, this is the URL Chrome is silently hitting to fix the chain for you. Fetch it manually to confirm whether the intermediate is available and current.
The SSL Labs trap
Qualys SSL Labs is excellent, but it has a blind spot here that trips people up. SSL Labs can hand you an A rating while noting "chain issues" in a less prominent section of the report. It does its own chain building and will flag an incomplete chain, but the overall grade might still look healthy because the leaf certificate is valid, the cipher suite is strong, and the protocol configuration is correct. You need to read the chain section, not just glance at the letter grade.
I've seen teams wave off a chain warning because "we got an A." That A doesn't mean curl will work. It means your cryptographic configuration is solid. Chain completeness is a different question.
The operational pattern that creates this
The scenario plays out the same way nearly every time. A certificate is approaching expiry. Someone—maybe a human, maybe an automated pipeline—renews it. The new leaf certificate gets deployed. But the intermediate either isn't updated or isn't included in the bundle.
If the CA changed their intermediate during the certificate's lifetime (which happens more often than you'd think), the old intermediate may no longer validate the new leaf. If the old intermediate is still being served alongside the new leaf, the chain is broken at the intermediate level. If the new leaf requires a different intermediate and nobody updated the concatenated bundle, same result.
What keeps this cycle going is the feedback loop. The person deploying the certificate checks it in their browser. Works. They close the ticket. The monitoring system, if it checks certificate validity at all, either does AIA fetching itself or only validates the leaf's expiry date without testing chain completeness. Everything looks green.
Then at some inconvenient hour, an API consumer's integration starts failing. A mobile app's certificate pinning rejects the new chain because the intermediate changed. A partner's webhook client can't complete the handshake. And now you're debugging a production incident that could have been caught at deploy time.
With certificate lifetimes dropping under SC-081v3—200-day certs are already the norm as of March 2026, with 100-day certs coming in 2027 and 47-day certs by 2029—this renewal cycle is accelerating fast. Every renewal is another chance to break the chain. What used to happen once a year during a manual renewal will soon happen every few weeks in an automated pipeline, and a misconfigured pipeline breaks every certificate it manages.
Building chain validation into the pipeline
The fix is straightforward: validate the full certificate chain from a non-browser perspective before you consider a deployment complete.
Your deployment pipeline should include a post-deployment check that connects to the endpoint using a TLS client that doesn't perform AIA fetching and validates the complete chain. This is what openssl s_client does, and what any competent TLS probe does. If the chain doesn't validate without fetching missing intermediates, the deployment should fail—or at minimum fire an alert.
Your ongoing monitoring needs to test chain completeness too, not just certificate expiry. Knowing a certificate expires in 30 days is useful. Knowing the chain is currently broken is urgent. These are different checks. Most monitoring setups only do the first.
And if you're managing certificates across multiple environments, you need visibility into what chain each endpoint is actually serving. Not what the certificate file contains on disk—what a client receives during the TLS handshake. Load balancers, CDNs, and reverse proxies can all modify or reconstruct the chain, and the only way to know what's actually being served is to probe the endpoint from outside.
This is where CertPulse's endpoint monitoring comes in. The TLS probes connect like a non-browser client, validate the full chain, and flag incomplete chains as a distinct finding from expiry warnings. When you're managing hundreds of endpoints across cloud providers and external services, having every probe validate chain completeness on a regular schedule is the difference between catching this at scan time and catching it when a customer's integration breaks.
What this comes down to
An incomplete certificate chain feels like it shouldn't still be a problem in 2026. The TLS spec is clear about what servers should send. CAs provide intermediate certificates with every issuance. Every tutorial on configuring Nginx or Apache mentions certificate bundling.
It keeps happening because the most common validation tool—opening a URL in a browser—is the one tool that hides the problem. The gap between "works in Chrome" and "works everywhere" is real, it's common, and it will only get more frequent as renewal cycles speed up.
If your monitoring only tells you a certificate is valid and not expired, you have a blind spot. Test the chain. Test it from something that won't do you the favor of fixing your mistakes. Test it automatically, on every renewal, from outside your network. Your API consumers won't have to file a ticket at 2 AM. That's the goal.
This is why we built CertPulse
CertPulse connects to your AWS, Azure, and GCP accounts, enumerates every certificate, monitors your external endpoints, and watches Certificate Transparency logs. One dashboard for every cert. Alerts when auto-renewal fails. Alerts when certs approach expiry. Alerts when someone issues a cert for your domain that you didn't request.
If you're looking for complete certificate visibility without maintaining scripts, we can get you there in about 5 minutes.