· 4 min read
How to Fix an Incomplete TLS Certificate Chain
Heshan Fernando
Co-founder & COO
The site loads perfectly in your browser. The mobile app can’t connect. curl reports a verification failure. Someone insists the certificate is fine because they can see the padlock.
They’re both right. This is the signature of a missing intermediate certificate, and the reason it’s so confusing is that browsers work around it and almost nothing else does.
What a chain is and why order matters
A TLS certificate doesn’t stand alone. It’s signed by an intermediate certificate authority, which is signed by another, up to a root that the client already trusts.
Validation walks that chain: each certificate’s issuer field must match the subject of the next one, ending at a trusted root. If any link is missing, the client can’t build a path to something it trusts, and verification fails.
Servers send the chain in the order it’s presented in the bundle, and most expect leaf first, then intermediates in ascending order. A bundle containing all the right certificates in the wrong order still fails on plenty of clients, which is why “but they’re all in there” isn’t a diagnosis.
The root is the exception. Clients already have it in their trust store. Including it adds bytes to every handshake and proves nothing, so conventionally it’s left out.
Why browsers hide the problem
Browsers cache intermediates from previous connections, and some will fetch a missing one using the AIA extension in the certificate. So a browser that has visited any other site using the same intermediate already has what it needs.
Nothing else does that reliably. curl, most language HTTP clients, mobile apps, and payment gateways all fail. That’s why an incomplete chain typically presents as “the website is fine but the API is broken”, and why it should always be tested from outside a browser.
Why people get stuck here
- Browser testing. The one client that papers over the fault is the one everyone tests with.
- Confusing bundle files.
cert.pem,chain.pem,fullchain.pem— which one the server wants varies by server. - Manual concatenation. Pasting certificates together in whatever order they arrived in the email.
- Renewals that quietly change the chain. An authority can change its intermediate, and a renewal picks up a new one.
What a correct chain looks like
Leaf first, intermediates in order
Server certificate, then the intermediate that signed it, then the one that signed that. Each certificate’s issuer matches the next one’s subject.
Root omitted
The client has it. Sending it wastes handshake bytes on every connection.
Verified from a machine that isn’t yours
Test with curl from a clean environment, or with an external checker. Your own machine may already hold the intermediate from something else.
| File | Contains | Used By |
|---|---|---|
cert.pem | Leaf only | Servers wanting cert and chain separately |
chain.pem | Intermediates only | Paired with cert.pem |
fullchain.pem | Leaf plus intermediates | Most common single-file option |
Common mistakes to avoid
- Testing only in a browser and concluding the chain is complete.
- Including the root certificate, which adds size for no benefit.
- Concatenating certificates in reverse order, which fails on stricter clients.
- Forgetting to reload the server after replacing the file — the old chain stays in memory.
- Letting a renewal go out without re-testing the chain, since intermediates do change.
How to do it with Certificate Chain Builder
The Certificate Chain Builder orders the certificates and verifies each link, in your browser.
- Paste the certificates you were issued, in any order.
- Choose whether to include the root — for most servers, don’t.
- Check that each issuer matches the next subject; a broken link is reported rather than silently ordered.
- Deploy the ordered bundle, reload the server, and test with curl from outside your network.
Certificates are public by design, so pasting them is safe — private keys never should be, here or anywhere. Other security utilities are in the tools directory.
Frequently asked questions
Why does my site work in a browser but fail everywhere else?
Browsers cache intermediates and can fetch missing ones. Other clients don’t. A chain missing an intermediate fails first for curl, mobile apps and API consumers, while the browser keeps showing a padlock.
Should the root certificate be in the bundle?
Usually not. Clients already trust it, so including it adds handshake overhead without improving validation. The Mozilla CA program documents which roots are in the common trust stores.
How do I test the chain properly?
openssl s_client -connect host:443 -showcerts from a machine that hasn’t visited the site, or an external SSL checker. Both show what the server actually sends rather than what your browser has assembled.
Final thought
If it works in Chrome and fails in curl, stop debugging the application. That specific pattern means an incomplete chain almost every time.