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.

Advertisement

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:

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:

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 --fullchain flag automatically produces a combined file. Check /etc/letsencrypt/live/example.com/fullchain.pem — it already includes the intermediate.

Related Guides