
Your SSL certificate is valid and hasn't expired, but browsers are showing a security warning. The most likely culprit: an incomplete certificate chain. Your server is sending your domain certificate but not the intermediate certificates that connect it to a trusted root.
Certificate authorities don't sign your certificate directly with their root certificate — roots are kept offline for security. Instead, they use intermediate certificates that chain from the root to your certificate:
Root CA (trusted by browsers)
└── Intermediate CA
└── Your domain certificate
Browsers trust the root. When they receive your certificate, they need to trace a path from your certificate back to a trusted root. If the intermediate is missing, the chain breaks.
Most modern browsers attempt to fetch missing intermediates automatically (AIA chasing), which is why the error is intermittent in some browsers and consistent in others. See why HTTPS works in one browser but fails in another for the full explanation of browser-specific behaviour.
Mobile browsers, older browsers, and strict TLS clients (like curl with --verify-peer) don't do AIA chasing — they fail immediately if intermediates are missing.
# Check the chain being served by your server
openssl s_client -connect yourdomain.com:443 -servername yourdomain.com
# Look in the output for "Certificate chain"
# A complete chain shows 2+ certificates (leaf + at least one intermediate)
# An incomplete chain shows only 1 certificate (just the leaf)
# More targeted check
echo | openssl s_client -connect yourdomain.com:443 2>/dev/null \
| openssl x509 -noout -text | grep -A2 "Issuer:"
You can also use SSL Labs' server test — it explicitly reports "Chain issues: Incomplete" if intermediates are missing, and grades you down for it.
A common field to check is the Authority Information Access (AIA) extension in the certificate itself:
echo | openssl s_client -connect yourdomain.com:443 2>/dev/null \
| openssl x509 -noout -text | grep -A3 "Authority Information"
# Look for: CA Issuers - URI:http://... — this is where the intermediate can be fetched
Your certificate file needs to include both your domain certificate and the intermediate certificate(s), concatenated in order (leaf first, intermediates after):
# Create the full chain file
cat yourdomain.crt intermediate.crt > yourdomain-fullchain.crt
# Or if your CA provides a bundle file
cat yourdomain.crt ca-bundle.crt > yourdomain-fullchain.crt
In your Nginx config:
server {
listen 443 ssl;
server_name yourdomain.com;
# Use the full chain file, not just the domain cert
ssl_certificate /etc/nginx/ssl/yourdomain-fullchain.crt;
ssl_certificate_key /etc/nginx/ssl/yourdomain.key;
}
After updating:
nginx -t && systemctl reload nginx
<VirtualHost *:443>
ServerName yourdomain.com
SSLEngine on
SSLCertificateFile /etc/apache2/ssl/yourdomain.crt
SSLCertificateKeyFile /etc/apache2/ssl/yourdomain.key
# Apache < 2.4.8: use SSLCertificateChainFile for intermediates
SSLCertificateChainFile /etc/apache2/ssl/intermediate.crt
# Apache >= 2.4.8: concatenate into SSLCertificateFile instead
# SSLCertificateFile /etc/apache2/ssl/yourdomain-fullchain.crt
</VirtualHost>
const https = require('https');
const fs = require('fs');
const options = {
key: fs.readFileSync('/etc/ssl/private/yourdomain.key'),
// cert must include the full chain
cert: fs.readFileSync('/etc/ssl/certs/yourdomain-fullchain.crt'),
// Or pass separately:
// cert: fs.readFileSync('/etc/ssl/certs/yourdomain.crt'),
// ca: fs.readFileSync('/etc/ssl/certs/intermediate.crt'),
};
https.createServer(options, app).listen(443);
Certbot generates a fullchain.pem file that already includes the intermediate. Always use fullchain.pem rather than cert.pem in your web server config:
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
Using cert.pem instead of fullchain.pem is one of the most common sources of incomplete chain errors with Let's Encrypt.
# Confirm the chain is complete
echo | openssl s_client -connect yourdomain.com:443 -servername yourdomain.com 2>/dev/null \
| grep -E "Certificate chain|depth|verify"
# Expected output with a complete chain:
# depth=2 ... (root)
# depth=1 ... (intermediate)
# depth=0 CN = yourdomain.com (your cert)
# Verify return code: 0 (ok)
An incomplete chain can emerge after certificate renewal if the renewal process doesn't correctly include intermediates — particularly with manual renewal scripts that copy only the domain certificate. Domain Monitor monitors your SSL certificate health including chain validity, catching incomplete chains before they cause user-facing browser warnings. Create a free account.
Wildcard, SAN (multi-domain), and single-domain SSL certificates cover different use cases. Here's a clear comparison to help you pick the right type — and avoid paying for coverage you don't need.
Read moreDNS resolves correctly from your office but fails for users in other countries or on different ISPs. Here's why geographic DNS inconsistency happens and how to diagnose which layer is causing it.
Read moreRegistrar lock and transfer lock are often confused — and disabling the wrong one leaves your domain vulnerable. Here's a clear breakdown of what each does and when to use them.
Read moreLooking to monitor your website and domains? Join our platform and start today.