SSL errors have a reputation for being cryptic, but most of them fall into a handful of categories once you recognize the patterns. I’ve diagnosed enough certificate issues to know that 90% of problems come from five root causes: expired certificates, wrong domain, broken chain, clock skew, and protocol/cipher mismatches.
Here’s how to diagnose and fix each one.
NET::ERR_CERT_DATE_INVALID — Certificate Expired
The certificate’s NotAfter date has passed. Check the expiry:
openssl x509 -in certificate.crt -noout -dates
Or check against a live server:
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -dates
Fix: Replace the certificate. If you’re using Let’s Encrypt, run sudo certbot renew --force-renewal. If you’re using a commercial certificate, request a new one from your CA.
This error can also appear in reverse — a certificate with a NotBefore date in the future. This happens when a certificate is issued but the server’s clock is behind. Check the server clock:
date
timedatectl status
If the clock is wrong, fix NTP synchronization first:
sudo timedatectl set-ntp true
NET::ERR_CERT_COMMON_NAME_INVALID — Domain Mismatch
The domain in the browser’s address bar doesn’t match any domain in the certificate. Check what the certificate actually covers:
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -text | grep -A 1 "Subject Alternative Name"
Common causes:
- Wrong certificate installed (for a different domain)
- Accessing the site by IP address, and the cert doesn’t include that IP in SANs
- Missing
www.variant (cert coversexample.combut notwww.example.comor vice versa) - Wildcard depth issue (
*.example.comdoesn’t coversub.api.example.com)
Fix depends on the cause: reissue the cert with the correct domains, or access the site via a hostname that’s in the cert.
One edge case: this error appears in modern browsers when the CN field has the domain but there are no SANs. The fix is to reissue with explicit SANs. All modern CAs include SANs by default, so this mostly occurs with old self-signed certificates.
NET::ERR_CERT_AUTHORITY_INVALID — Untrusted Certificate
The browser doesn’t trust the CA that issued the certificate. Three common causes:
1. Self-signed certificate — The cert was signed by nobody a browser trusts. If this is intentional (development, internal tools), trust the CA locally. If it’s unintentional, replace with a CA-signed certificate.
2. Missing intermediate certificate — Your CA chain is incomplete. The browser can’t build a trust path from your cert to a trusted root. Verify the chain:
openssl s_client -connect example.com:443 -servername example.com -showcerts 2>/dev/null | grep -E "s:|i:"
You should see multiple certificates in the chain (depth 0, 1, and possibly 2). If you only see depth 0 (your domain cert), the chain is incomplete. Add the intermediate cert(s) to your server’s certificate configuration.
For Nginx — make sure ssl_certificate points to a fullchain file (domain cert + intermediate):
cat certificate.crt intermediate.crt > fullchain.pem
For Apache — set SSLCertificateChainFile to the intermediate CA file.
3. Expired intermediate or root certificate — Rare but happens. Check each cert in the chain:
openssl s_client -connect example.com:443 -showcerts 2>/dev/null | openssl x509 -noout -dates
If an intermediate is expired, you need a new certificate issued against a valid CA chain.
SSL_ERROR_RX_RECORD_TOO_LONG — Protocol Mismatch
This Firefox error (or similar in other browsers: ERR_SSL_PROTOCOL_ERROR) usually means the server responded with plain HTTP on the SSL/TLS port. Classic scenario: a service is running on port 80 but you’re hitting port 443.
Check what’s listening:
sudo ss -tlnp | grep 443
If nothing is listening on 443, that’s your problem. Start your web server and check the SSL configuration.
Another cause: your web server is listening on 443 but not configured for SSL. In Nginx, check that ssl (or http2 which implies ssl) is in the listen directive:
listen 443 ssl;
# NOT just: listen 443;
SSL_ERROR_HANDSHAKE_FAILURE_ALERT — Cipher/Protocol Negotiation Failed
The client and server have no TLS version or cipher suite in common. This usually appears with:
- Very old clients trying to connect to modern servers that dropped old protocols
- Very strict clients rejecting older ciphers your server still offers
Diagnose by testing specific protocol versions:
# Test if TLS 1.2 is supported
openssl s_client -connect example.com:443 -tls1_2
# Test if TLS 1.3 is supported
openssl s_client -connect example.com:443 -tls1_3
# Test specific cipher
openssl s_client -connect example.com:443 -cipher ECDHE-RSA-AES256-GCM-SHA384
If specific protocol versions fail, check your server’s SSLProtocol (Apache) or ssl_protocols (Nginx) directives.
For clients that need TLS 1.0 or 1.1 (old internal systems, legacy equipment), you can temporarily re-enable them, but document why and plan to phase out those clients.
OCSP Stapling Errors
If you see warnings about OCSP in your server logs, the server couldn’t reach the CA’s OCSP responder. Check your resolver configuration:
Nginx:
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
Without a resolver, Nginx can’t do OCSP lookups. If your server is on a network that blocks outbound port 80 (OCSP uses HTTP), stapling will fail. You can disable it if it’s causing issues:
ssl_stapling off;
Certificate Pinning Failures in Mobile Apps
If your API is serving a valid certificate but a mobile app refuses to connect, it might use certificate pinning — the app has a hardcoded hash of the expected certificate. When you renew or replace the certificate, the hash changes and the app rejects it.
This is a mobile app deployment problem, not a certificate problem. The app needs to be updated with the new certificate hash, or pinning needs to implement a rotation strategy (pinning to the CA’s public key rather than the leaf certificate).
Check if pinning is in play:
# Test connection without and with SNI to see different behavior
openssl s_client -connect api.example.com:443
If the certificate looks fine from the command line but the app still rejects it, pinning is likely involved.
Debugging the Full Handshake
When you can’t figure out where a TLS problem is, capturing the full handshake gives you the most information:
openssl s_client -connect example.com:443 \
-servername example.com \
-showcerts \
-debug \
2>&1 | head -100
Or use Wireshark to capture the TLS handshake at the network level. This is especially useful for debugging mutual TLS (mTLS) where both client and server authenticate with certificates.
Quick Reference: Diagnosis Commands
# Full certificate details
openssl x509 -in cert.crt -text -noout
# Check what a live server is serving
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -text -noout
# Verify cert and key match
openssl x509 -noout -modulus -in cert.crt | md5sum
openssl rsa -noout -modulus -in private.key | md5sum
# Check chain completeness
openssl s_client -connect example.com:443 -showcerts 2>/dev/null | grep -E "s:|i:|depth"
# Check expiry
echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -enddate
# Verify cert against CA
openssl verify -CAfile ca-bundle.crt certificate.crt
Most SSL problems are fixable in under 30 minutes once you know which command to run. The hard part is usually identifying which of the five root causes you’re dealing with. These commands give you enough information to make that determination quickly.