How to Check Your SSL Certificate Chain
A complete SSL chain has three parts: your domain certificate (leaf), an intermediate CA certificate, and a root CA certificate. Missing the intermediate is the most common SSL misconfiguration — desktop browsers cache it and work fine, but mobile browsers and API clients fail. Check with openssl s_client -connect domain.com:443 -showcerts.
The most painful SSL bug: you renew your certificate, desktop Chrome works, you close the ticket — three hours later your mobile team reports all API calls are failing. Missing intermediate certificate. Desktop cached it. Mobile didn't.
Visualize your SSL chain →The three parts of an SSL trust chain
| Part | What it is | Where it lives |
|---|---|---|
| Leaf certificate | Your domain's certificate | Your server sends it |
| Intermediate CA | Issued by root, issues your cert | Your server must send it |
| Root CA | Pre-installed in browsers | Already in the trust store |
Browsers build the trust path: Leaf → Intermediate → Root. If the intermediate is missing, the browser can't verify your certificate and shows an error — unless it has the intermediate cached from a previous connection.
Check your chain with openssl
# Show the full certificate chain your server sends openssl s_client -connect example.com:443 -showcerts 2>/dev/null | grep "s:" # Output should show 2-3 lines like: # s:CN = example.com ← your cert # s:C = US, O = Let's Encrypt, CN = R3 ← intermediate # s:O = Internet Security Research Group ← root (sometimes omitted)
# Check with curl (simulates API client behavior) curl -sI https://example.com # If this works but mobile fails, you have a chain issue
Fix: Nginx — use fullchain.pem not cert.pem
The most common cause of missing intermediate with Let's Encrypt:
# Wrong — leaf cert only ssl_certificate /etc/letsencrypt/live/example.com/cert.pem; # Correct — fullchain.pem includes leaf + intermediate ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# After fixing, reload: nginx -t && systemctl reload nginx
Fix: Apache
# Apache 2.4.8+ — use fullchain.pem SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem # Older Apache — specify chain file separately SSLCertificateFile /etc/letsencrypt/live/example.com/cert.pem SSLCertificateChainFile /etc/letsencrypt/live/example.com/chain.pem
Fix: Build fullchain manually
If you have separate cert files from a commercial CA:
# Combine leaf + intermediate into fullchain cat your_domain.crt intermediate.crt > fullchain.crt # Verify the chain openssl verify -CAfile root.crt -untrusted intermediate.crt your_domain.crt
Certificate expiry — check proactively
# Check expiry date echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -dates # Output: # notBefore=Jan 1 00:00:00 2026 GMT # notAfter=Apr 1 00:00:00 2026 GMT ← this is the date that matters
Let's Encrypt auto-renewal
# Check if auto-renewal is configured systemctl status certbot.timer # Test renewal without actually renewing certbot renew --dry-run # Force renewal if less than 30 days left certbot renew --force-renewal nginx -t && systemctl reload nginxVisualize your SSL chain →