The #1 SSL Mistake: Wrong Certificate Chain Order
You just installed a brand-new SSL certificate. Chrome shows the padlock. You ship it. Then the alerts roll in: your API integration is returning unable to get local issuer certificate, Android users see security warnings, and curl refuses to connect.
The certificate is valid. The problem is the order you served it in.
What "certificate chain" actually means
When a browser connects to your server over TLS, your server doesn't just send its own certificate. It sends a chain of certificates that links your leaf certificate back to a trusted root CA. The chain typically looks like this:
- Leaf certificate -- your domain's certificate
- Intermediate certificate(s) -- issued by the root CA, used to sign your leaf
- Root certificate -- pre-installed in the client's trust store
Your server needs to send the leaf and intermediates. The root is already on the client, so sending it is optional (and slightly wasteful, but harmless).
Why the order matters
The TLS spec (RFC 5246, section 7.4.2) is explicit: the sender's certificate MUST come first, and each following certificate MUST directly certify the one preceding it. In other words: leaf first, then intermediates, with each cert signing the one above it.
Correct order:
1. yoursite.com (leaf -- signed by intermediate)
2. Intermediate CA (signed by root)
3. Root CA (optional -- already in trust store)
Wrong order (and the most common mistake):
1. Intermediate CA
2. yoursite.com
Or just the leaf with no intermediates at all.
Why it "works" in Chrome but fails everywhere else
Modern browsers are forgiving. Chrome and Firefox implement AIA fetching -- if an intermediate is missing, they follow the Authority Information Access URL in the certificate to download it. So Chrome fills in the gap silently.
These clients do not do AIA fetching:
curlandwget- Java's
HttpsURLConnectionand most JVM HTTP clients - Python's
requests/urllib3 - Go's
net/http - OpenSSL-based tools
- Older Android browsers (pre-10)
- Many IoT devices and embedded systems
If your chain is wrong, every automated system hitting your API will fail while your browser looks fine. This is exactly why it's the number one SSL mistake -- you won't catch it by clicking around in Chrome.
How to diagnose it
Check with openssl s_client
openssl s_client -connect yoursite.com:443 -servername yoursite.com </dev/null 2>&1
Look at the certificate chain output:
Certificate chain
0 s:CN = yoursite.com
i:C = US, O = Let's Encrypt, CN = R3
1 s:C = US, O = Let's Encrypt, CN = R3
i:C = US, O = Internet Security Research Group, CN = ISRG Root X1
That's correct: certificate 0 is the leaf, certificate 1 is the intermediate, and each i: (issuer) matches the next s: (subject).
If you see this instead:
Certificate chain
0 s:CN = yoursite.com
i:C = US, O = Let's Encrypt, CN = R3
---
Verify return code: 21 (unable to verify the first certificate)
You're missing the intermediate. The server only sent the leaf.
Check the verify return code
Verify return code: 0 (ok)-- chain is complete and validVerify return code: 2 (unable to get issuer certificate)-- missing intermediateVerify return code: 21 (unable to verify the first certificate)-- missing intermediate
Quick test with curl
curl -vI https://yoursite.com 2>&1 | grep -E "SSL|certificate"
If curl fails with SSL certificate problem: unable to get local issuer certificate, your chain is broken.
How to fix it
The fix is the same everywhere: concatenate your certificates in the right order into one file.
Build the correct chain file
cat yoursite.crt intermediate.crt > fullchain.pem
If you have multiple intermediates (rare but possible):
cat yoursite.crt intermediate1.crt intermediate2.crt > fullchain.pem
Verify the result:
openssl crl2pkcs7 -nocrl -certfile fullchain.pem | \
openssl pkcs7 -print_certs -noout
This prints each certificate's subject and issuer. Confirm the chain links up correctly.
nginx
server {
listen 443 ssl;
server_name yoursite.com;
ssl_certificate /etc/nginx/ssl/fullchain.pem; # leaf + intermediates
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
}
nginx uses a single ssl_certificate directive that must point to the concatenated chain file. There is no separate directive for intermediates.
Reload:
nginx -t && systemctl reload nginx
Apache
<VirtualHost *:443>
ServerName yoursite.com
SSLCertificateFile /etc/ssl/certs/yoursite.crt
SSLCertificateKeyFile /etc/ssl/private/yoursite.key
SSLCertificateChainFile /etc/ssl/certs/intermediate.crt
</VirtualHost>
Apache 2.4.8+ also supports putting the full chain in SSLCertificateFile (like nginx), but the explicit SSLCertificateChainFile directive makes the setup clearer.
Reload:
apachectl configtest && systemctl reload apache2
Node.js
const https = require('https');
const fs = require('fs');
const options = {
key: fs.readFileSync('/etc/ssl/private/yoursite.key'),
cert: fs.readFileSync('/etc/ssl/certs/fullchain.pem'),
// Or specify the chain separately:
// cert: fs.readFileSync('/etc/ssl/certs/yoursite.crt'),
// ca: [fs.readFileSync('/etc/ssl/certs/intermediate.crt')],
};
https.createServer(options, app).listen(443);
The cert option accepts a full chain PEM. Alternatively, put only the leaf in cert and intermediates in ca.
Verify the fix
After reloading your server config, confirm the chain is correct:
# Full chain check
openssl s_client -connect yoursite.com:443 -servername yoursite.com </dev/null 2>&1 | \
grep -E "Certificate chain|Verify return"
# Should show: Verify return code: 0 (ok)
# Also test with curl
curl -I https://yoursite.com
Quick reference
| Symptom | Cause | Fix |
|---|---|---|
| Works in Chrome, fails in curl | Missing intermediate | Add intermediate to chain file |
Verify return code: 21 |
Incomplete chain | Concatenate leaf + intermediates |
| Chain shows wrong order | Certs concatenated backwards | Reorder: leaf first, then intermediates |
| Extra root cert in chain | Harmless but wastes bytes | Optionally remove, not required |
Getting the chain order right is a one-time fix. Get it right, verify with openssl s_client, and you'll never get another 3 AM alert about SSL failures in your API clients.