You installed an SSL certificate, restarted your web server, and pointed your browser at https:// — yet some visitors on mobile devices still see a security warning, and your API clients throw SSL: CERTIFICATE_VERIFY_FAILED. The most likely cause is an incomplete certificate chain. Understanding how certificate chains work is the fastest way to diagnose and permanently fix this class of SSL error.

What Is an SSL Certificate Chain?

An SSL (or TLS) certificate chain — also called the chain of trust — is an ordered sequence of digital certificates that connect your domain's server certificate to a root certificate authority (Root CA) that browsers and operating systems trust unconditionally.

A standard chain has three tiers:

When a browser connects over HTTPS, it performs chain validation: it walks up the chain from your server certificate through each intermediate until it finds a root CA in its trust store. If any link is missing or the chain cannot be verified, the browser displays a security warning.

Why Root CAs Do Not Issue Certificates Directly

This design is a deliberate security decision. A Root CA's private key is extraordinarily valuable — if it were stolen or misused, every certificate ever issued by that CA worldwide would need to be revoked, and every device on the planet would need a trust store update. That would be catastrophic.

To mitigate this risk, root CA private keys are stored offline in Hardware Security Modules (HSMs) inside physically secured, air-gapped facilities. Root CAs only sign intermediate CA certificates, which are then used for day-to-day issuance. If an intermediate CA is compromised, the CA can revoke just that intermediate and issue a replacement, while the root remains untouched.

Additional benefits of the intermediate layer include:

What Is an Incomplete Chain and Why Does It Happen?

An incomplete chain occurs when your web server sends only the server certificate during the TLS handshake, without including the intermediate certificate(s) that the browser needs to verify the chain up to a trusted root.

Common causes:

Why Desktop Browsers Sometimes Hide the Problem

Modern desktop browsers aggressively cache intermediate certificates. When your browser visits any website that uses the same intermediate CA, it stores that intermediate locally. Later, when it visits your site with an incomplete chain, it can reconstruct the chain from its cache — and shows a green padlock. This creates a deceptive situation: the server admin sees green on their laptop but mobile users on fresh devices (with empty caches) see a warning. This is why testing from a known-clean environment is essential.

Diagnosing the Chain with OpenSSL

OpenSSL is the most reliable tool for checking what your server is actually sending:

Step 1 — Check the number of certificates sent

openssl s_client -connect yourdomain.com:443 -showcerts 2>&1 | grep -c "BEGIN CERTIFICATE"

A result of 1 means incomplete chain. A result of 2 or 3 means the intermediate(s) are being sent correctly.

Step 2 — Verify each depth level

openssl s_client -connect yourdomain.com:443 -showcerts 2>&1 | grep -E "depth|verify"

Healthy output:

depth=2 C = US, O = DigiCert Inc, CN = DigiCert Global Root CA
verify return:1
depth=1 C = US, O = DigiCert Inc, CN = RapidSSL TLS RSA CA G1
verify return:1
depth=0 CN = yourdomain.com
verify return:1

Step 3 — Read the certificate details

openssl x509 -in /path/to/certificate.crt -text -noout | grep -E "Issuer|Subject|Not After|DNS:"

Step 4 — Confirm private key matches certificate

# Both md5 values must be identical
openssl x509 -noout -modulus -in certificate.crt | openssl md5
openssl rsa  -noout -modulus -in private.key    | openssl md5
Chain State OpenSSL Signal Client Impact Fix
Complete chain depth=2, verify OK at all levels Green padlock everywhere None needed
Incomplete chain Only depth=0; intermediate missing Mobile & API errors, browser warning Add CA bundle / intermediate
Wrong order Chain present but in wrong sequence Intermittent client errors Reorder: server → intermediate → root
Expired intermediate verify error: certificate has expired All clients fail Update CA bundle from provider

Fixing an Incomplete Chain on Apache

The recommended approach for Apache 2.4.8+ is to create a single fullchain.pem that includes both your server certificate and the intermediate, in that order:

Method 1 — Single fullchain.pem (recommended)

# Server certificate must come FIRST
cat certificate.crt intermediate.crt > /etc/ssl/certs/yourdomain.fullchain.pem

# In your VirtualHost block
<VirtualHost *:443>
    ServerName yourdomain.com
    SSLEngine on
    SSLCertificateFile    /etc/ssl/certs/yourdomain.fullchain.pem
    SSLCertificateKeyFile /etc/ssl/private/yourdomain.key
</VirtualHost>

Method 2 — SSLCertificateChainFile (Apache < 2.4.8 only)

<VirtualHost *:443>
    SSLEngine on
    SSLCertificateFile      /etc/ssl/certs/certificate.crt
    SSLCertificateKeyFile   /etc/ssl/private/private.key
    SSLCertificateChainFile /etc/ssl/certs/ca_bundle.crt
</VirtualHost>

Always test before reloading:

apachectl configtest   # must return "Syntax OK"
systemctl reload apache2

Fixing an Incomplete Chain on Nginx

Nginx requires a single file combining the server certificate and all intermediates. The order matters — server certificate first, then intermediates in descending order:

# Build fullchain.pem
cat yourdomain.crt intermediate.crt > /etc/nginx/ssl/yourdomain.fullchain.pem

# Server block
server {
    listen 443 ssl http2;
    server_name yourdomain.com;

    ssl_certificate     /etc/nginx/ssl/yourdomain.fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/yourdomain.key;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers   HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;
}
nginx -t                # must show "syntax is ok" and "test is successful"
systemctl reload nginx

Let's Encrypt — Using the Right File

Certbot creates four files inside /etc/letsencrypt/live/yourdomain/. The single most common misconfiguration is using the wrong one:

# Correct Nginx config for Let's Encrypt
ssl_certificate     /etc/letsencrypt/live/yourdomain/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain/privkey.pem;

# WRONG — cert.pem is missing the intermediate
ssl_certificate /etc/letsencrypt/live/yourdomain/cert.pem;

Verify auto-renewal is working:

certbot renew --dry-run

Installing via DirectAdmin (Shared Hosting)

On AsiaGB shared hosting (powered by DirectAdmin), you manage SSL through the control panel without touching config files:

  1. Log in to DirectAdmin and navigate to SSL Certificates
  2. Choose Paste a pre-generated certificate and key
  3. Paste your Server Certificate in the Certificate field
  4. Paste your Private Key in the Private Key field
  5. Enable Use a CA Cert and paste the entire contents of ca_bundle.crt or intermediate.crt in the CA Certificate field
  6. Click Save

If the provider gave you a .zip file, extract it and look for a file named *ca-bundle*, *intermediate*, or *chain*. That is the file to paste in the CA Certificate field.

Pro tip: After updating your certificate chain, always test from a device or machine that has never visited your site before — or use a private/incognito window on a different network. Desktop browsers cache intermediates aggressively, so testing on your own machine may show a valid padlock even when the chain is still broken for everyone else. Running openssl s_client -connect yourdomain.com:443 -showcerts 2>&1 | grep -c "BEGIN CERT" from a server gives a definitive, cache-free answer.

OCSP Stapling: Chain Quality Affects TLS Performance

A correctly assembled certificate chain is a prerequisite for enabling OCSP Stapling. OCSP Stapling lets your server pre-fetch a signed revocation status response from the CA and attach it to the TLS handshake, eliminating the need for the client to make a separate OCSP request. This reduces latency on the first connection and improves user privacy by preventing the CA from learning which sites visitors are connecting to.

# Enable OCSP Stapling on Nginx
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/nginx/ssl/yourdomain.fullchain.pem;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
# Enable OCSP Stapling on Apache
SSLUseStapling On
SSLStaplingCache shmcb:/tmp/stapling_cache(128000)

Without a complete chain, the server cannot determine the OCSP responder URL embedded in the intermediate certificate, causing stapling to silently fail.

Frequently Asked Questions

What is an SSL certificate chain and why is an intermediate CA required?

An SSL certificate chain is a sequence of digital certificates that links your server certificate through one or more intermediate CAs to a root CA that browsers trust. Intermediate CAs are required because root CAs never issue certificates directly to websites — doing so would expose the root's private key. If an intermediate CA is compromised, only that intermediate needs to be revoked, leaving the root CA safe.

How do I check whether my certificate chain is complete?

Run openssl s_client -connect yourdomain.com:443 -showcerts and count the BEGIN CERTIFICATE blocks. A complete chain shows 2–3 certificates. You can also grep for verify return:1 at each depth level. If you only see depth=0, the intermediate is missing and clients that have not cached it will show a security warning.

How do I fix an incomplete chain on Apache and Nginx?

On Apache, concatenate your server certificate and intermediate into a single fullchain.pem file (server cert first), then point SSLCertificateFile at that file. Alternatively use SSLCertificateChainFile for Apache versions below 2.4.8. On Nginx, ssl_certificate must reference a fullchain.pem that already includes the intermediate. After editing, always run apachectl configtest or nginx -t before reloading the service.

Does Let's Encrypt have certificate chain issues?

Let's Encrypt provides a fullchain.pem that already bundles the server certificate and intermediate together. The most common mistake is using cert.pem (server cert only) instead of fullchain.pem in the web server configuration. Always reference fullchain.pem for ssl_certificate on Nginx or SSLCertificateFile on Apache. Certbot's auto-renew preserves these paths automatically.

All SSL Certificate Types at AsiaGB

DV, OV, EV, and Wildcard SSL starting from 1,000 THB/year with Installation Support included

View SSL Certificates