How do you troubleshoot TLS cert chain errors that work in browsers but fail in API calls?
Quick Answer
Browsers silently fetch missing intermediate certificates using the AIA extension embedded in the leaf certificate, and cache trusted intermediates from prior visits, so a server that forgets to send its intermediate certificate often still works fine in a browser. Most API clients, CLI tools, and backend libraries do not do this automatic fetching; they only trust what's explicitly presented during the TLS handshake, so the same misconfigured server fails chain validation for them. The fix is always on the server, configuring it to send the full chain, never a client-side workaround.
Detailed Answer
Imagine a bouncer at an exclusive club told to admit anyone showing a valid membership card and, if asked, a letter from the club's parent organization confirming the club itself is legitimate. Some visitors already have a copy of that confirmation letter in their wallet from a previous visit, so they don't need to be shown one again — they just flash their card and walk in, and it looks like the letter didn't even matter. A first-time visitor without that letter already in hand, though, gets stopped cold at the door if the club forgot to have a copy ready to show on the spot.
This is exactly the leaf, intermediate, root certificate chain problem. A server's TLS certificate, the leaf, is signed by an intermediate Certificate Authority certificate, which is itself signed by a root CA pre-trusted by operating systems and browsers. Browsers were engineered to be forgiving: modern browsers implement AIA chasing, meaning that if a server doesn't send the intermediate certificate, the browser follows a URL embedded in the leaf certificate, the Authority Information Access extension, to fetch the missing intermediate itself, and browsers also cache intermediates seen from previous connections to any site using the same CA. This forgiving behavior exists to maximize the chance a real user can browse the web without a broken-looking error page, even when server operators misconfigure their TLS setup.
Most non-browser clients — a Java HTTP client, Python's requests library, curl without special flags, mobile app TLS stacks — implement strict chain validation: they only build a trust path using exactly the certificates presented during the TLS handshake's Certificate message, plus their local trust store of root CAs. If the server's TLS configuration only serves the leaf certificate and omits the intermediate, these clients have no path from the leaf to a trusted root and fail validation outright, with an error like unable to get local issuer certificate, even though the exact same server works fine when checked in a browser that happened to have or fetch the missing piece on its own.
In production, the diagnostic step is openssl s_client -connect payments-api.internal:443 -showcerts, which shows exactly what the server actually sent during the handshake. If you see only one certificate in the output, the leaf, instead of the leaf plus one or more intermediates, that confirms the server is misconfigured. The fix is almost always concatenating the leaf certificate with the required intermediate certificate or certificates, in the correct order, into the server's configured certificate file, and reloading the web server or load balancer, never touching anything on the client side.
The gotcha: teams sometimes fix this by adding the missing intermediate to the client's trust store instead of fixing the server, which appears to solve the immediate problem but is fragile and wrong. It only fixes that one client, doesn't fix the underlying misconfiguration for every other API consumer or future service talking to the same endpoint, and if the CA ever rotates its intermediate certificate, which does happen periodically, the client-side workaround silently breaks again, while a properly-configured server would have kept working transparently through the rotation.