SSL Certificate Chain Incomplete: The Silent HTTPS Killer
Your SSL certificate is valid, the expiry date is months away, and openssl says the signature checks out. But visitors see "Your connection is not private" in Chrome, "Warning: Potential Security Risk" in Firefox, or a scary red screen in Safari. What gives?
The answer in 90% of cases: you are missing the intermediate certificate from the chain.
Understanding the Certificate Chain
An SSL certificate does not exist in isolation. It sits inside a chain of trust:
Root CA (trusted by browsers)
└── Intermediate CA (signed by Root)
└── Your Server Certificate (signed by Intermediate)
Browsers already ship with hundreds of root certificates in their trust store. But they do not have the intermediate. When your server sends only the leaf certificate without the intermediate, the browser cannot build a complete path back to a trusted root — and it rejects the connection.
The fix is straightforward: include the intermediate certificate in what your server presents to clients.
Step 1: Diagnose with OpenSSL
Connect to your server and inspect the certificate chain it sends back:
openssl s_client -connect example.com:443 -showcerts
Press Enter, then wait for the output. Look for the certificate blocks — each starts with -----BEGIN CERTIFICATE----- and ends with -----END CERTIFICATE-----. A properly configured server should send at least two certificates:
Certificate chain
0 s:/CN=example.com
i:/C=US/O=Let's Encrypt/CN=R3 # <-- This is the intermediate
1 s:/C=US/O=Let's Encrypt/CN=R3
i:/O=ISRG Root X1 # <-- This is the root (usually not sent)
---
If you only see 0 s:/CN=example.com with 1 being empty or absent, your server is sending just the leaf certificate — the chain is incomplete.
Step 2: Check What Is Missing
To verify the root cause, run the following command and look for the output:
openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt example.com.crt
If you get error 20 at 0 depth lookup: unable to get local issuer certificate, it confirms the server certificate cannot be verified because the intermediate is not present in the chain.
You can also check the issuer of your server certificate to identify which intermediate you need:
openssl x509 -in example.com.crt -noout -issuer
The output will show something like issuer = /C=US/O=Let's Encrypt/CN=R3. That tells you the intermediate you need.
Step 3: Get the Intermediate Certificate
Download the intermediate certificate from your Certificate Authority. Here are common sources:
- Let's Encrypt: The intermediate is usually bundled by Certbot automatically. If not, download from letsencrypt.org/certificates. The common intermediates are
R3(for ECDSA) orR10/R11(for RSA). - DigiCert: DigiCert Root & Intermediate Certificates
- Sectigo (Comodo): Sectigo Intermediate CA
- GlobalSign: GlobalSign Repository
- Cloudflare Origin CA: The origin certificate is self-signed by Cloudflare — no intermediate needed if behind Cloudflare proxy. If not proxied, use a real CA instead.
Save the intermediate certificate file, for example as intermediate.crt.
Step 4: Combine into a Full Chain File
The key insight is that the order matters. Your server certificate comes first, followed by the intermediate, followed by the root (optional — most servers exclude the root). Concatenate them:
# Your server cert first, then the intermediate
cat example.com.crt intermediate.crt > fullchain.crt
# If you have more than one intermediate (rare):
cat example.com.crt intermediate1.crt intermediate2.crt > fullchain.crt
Verify the combined file looks correct:
grep "BEGIN CERTIFICATE" fullchain.crt | cat -n
You should see each certificate numbered in order:
1 -----BEGIN CERTIFICATE----- # your server cert
2 -----BEGIN CERTIFICATE----- # intermediate
Step 5: Update Your Server Configuration
Point your web server to the full chain file instead of just the server certificate.
Nginx
# /etc/nginx/conf.d/example.com.conf or similar
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/ssl/certs/fullchain.crt; # <-- full chain here
ssl_certificate_key /etc/ssl/private/example.com.key;
# ... rest of your config
}
Test and reload:
nginx -t
nginx -s reload
Apache
# /etc/apache2/sites-available/example.com-le-ssl.conf
<VirtualHost *:443>
ServerName example.com
SSLEngine on
SSLCertificateFile /etc/ssl/certs/fullchain.crt # <-- full chain
SSLCertificateKeyFile /etc/ssl/private/example.com.key
# No need for SSLCertificateChainFile when using fullchain.crt
# ... rest of your config
</VirtualHost>
Test and reload:
apachectl configtest
systemctl reload apache2 # or: systemctl reload httpd
Step 6: Verify with Online Tools
After reloading, run the openssl check again to confirm:
openssl s_client -connect example.com:443 -showcerts </dev/null 2>/dev/null | grep "s:/" | head -3
Also use an external tool for a full validation:
- FixWikiHub SSL Checker: /en/tools/ssl-checker.html — verifies chain completeness, expiry, and protocol support.
- SSL Labs (Qualys): ssllabs.com/ssltest — the gold standard for full TLS analysis.
- KeyCDN SSL Checker: tools.keycdn.com/cert — quick visual chain display.
An SSL Labs grade of "A" or better means the chain is complete, protocols are modern, and there are no trust issues.
Common CAs and Intermediate Locations
| Certificate Authority | Intermediate Download URL | Notes |
|---|---|---|
| Let's Encrypt | letsencrypt.org/certificates | Usually handled by Certbot; uses R3/R10/R11 |
| DigiCert | digicert.com/kb/digicert-root-certificates.htm | Multiple intermediate generations; match your cert's issuer |
| Sectigo (Comodo) | sectigo.com/knowledge-base/ssl-certificates/intermediate-ca-certificates | Uses "Sectigo RSA Domain Validation" intermediates |
| GlobalSign | globalsign.com/en/repository/roots-and-intermediates | Check OV vs DV for correct intermediate |
| Amazon Trust Services | aws.amazon.com/certificate-manager/resources/ | ACM handles this automatically for ACM certs |
Pro tip: If you use Let's Encrypt with Certbot, the--fullchainflag automatically produces a combined file. Check/etc/letsencrypt/live/example.com/fullchain.pem— it already includes the intermediate.