Blog

Incomplete certificate chain: why your site works in Chrome but fails in curl

Your site loads fine in the browser. Then a customer's integration fails with unable to get local issuer certificate, the Android app throws CertPathValidatorException, or a webhook provider reports an SSL error. The certificate is valid and not expired. What's going on?

Almost always: the server isn't sending the intermediate certificate.

How a certificate chain works

Your certificate isn't signed directly by a root that clients trust. It's signed by an intermediate CA, which is signed by the root:

example.com            ← your certificate (leaf)
  └─ R11 / Sectigo DV / …  ← intermediate, YOU must send this
       └─ ISRG Root X1 / …  ← root, already in the client's trust store

Clients ship with roots, not intermediates. So during the handshake the server has to send the leaf plus every intermediate. If it sends only the leaf, the client can't connect it to a root.

Why browsers don't notice

Desktop browsers are forgiving. Chrome and Safari can download the missing intermediate from the URL in the certificate's Authority Information Access (AIA) field, and Firefox ships a list of known intermediates. They also cache intermediates from other sites you've visited. The page works, and the bug ships to production.

Most other clients do none of that: curl, wget, OpenSSL, Python requests, Java, Go, Node.js, many Android versions, IoT devices, payment and webhook providers. For them the chain is simply untrusted.

How to check

Run the domain through sslverify.net. If the server's chain alone doesn't verify but adding the AIA-downloaded intermediate does, you'll see “Certificate chain is incomplete” and the grade is capped at B. From a terminal:

openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null

Count the -----BEGIN CERTIFICATE----- blocks. One block usually means the intermediate is missing.

How to fix it

Serve the full chain file instead of the leaf alone:

Two related mistakes to avoid: putting the certificates in the wrong order (each one must be followed by its issuer), and adding the root. The root is harmless but useless, since clients already have it and it only makes every handshake larger. sslverify.net flags both.

Prevent it from coming back

Chains break most often during manual renewals and certificate swaps. Automate renewal, deploy the full-chain file, and re-check after every change.