Common SSL/TLS Errors and What They Actually Mean
SSL error messages are famously unhelpful. A single misconfiguration can produce different error strings depending on whether you're looking at Chrome, Firefox, curl, or OpenSSL. This guide maps the most common errors to their actual causes and tells you how to fix them.
Paste your certificate into our decoder to inspect its details -- issuer, validity dates, SANs, and chain information -- before you start debugging.
ERR_CERT_AUTHORITY_INVALID
What it means: The client doesn't trust the certificate authority that signed your certificate. The chain of trust from your leaf certificate back to a recognized root CA is broken.
Common causes:
- Self-signed certificate with no corresponding trust on the client
- Missing intermediate certificates in the chain file
- Internal/private CA certificate not installed on the client machine
- Certificate signed by a CA that was removed from trust stores (e.g., Symantec roots after 2018)
How to fix:
For public CAs, this almost always means your server isn't sending the intermediate certificate. Rebuild your chain file:
cat yoursite.crt intermediate.crt > fullchain.pem
Then verify the server sends the complete chain:
openssl s_client -connect yoursite.com:443 -servername yoursite.com </dev/null 2>&1
For self-signed or private CA certificates, you need to add the CA to the client's trust store. On Linux:
cp your-ca.crt /usr/local/share/ca-certificates/
update-ca-certificates
On macOS:
sudo security add-trusted-cert -d -r trustRoot \
-k /Library/Keychains/System.keychain your-ca.crt
ERR_CERT_DATE_INVALID
What it means: The certificate's validity period doesn't include the current date. Either the certificate has expired (notAfter is in the past) or it's not yet valid (notBefore is in the future).
Common causes:
- Certificate genuinely expired and wasn't renewed
- Server clock is wrong (more common than you'd think, especially on VMs after snapshot restore)
- Client clock is wrong (user's machine has incorrect time)
- An intermediate certificate in the chain expired (your leaf is fine, but the chain isn't)
How to fix:
Check the dates:
openssl x509 -in yoursite.crt -noout -dates
notBefore=Jan 1 00:00:00 2025 GMT
notAfter=Apr 1 23:59:59 2026 GMT
If the cert is expired, renew it. If using Let's Encrypt with certbot, renewal should be automatic -- check why it stopped:
certbot renew --dry-run
If the dates look fine, check your server's clock:
date -u
timedatectl status
If the clock is off, fix it:
timedatectl set-ntp true
systemctl restart systemd-timesyncd
Don't forget to check the intermediate dates too. An expired intermediate with a valid leaf produces this same error in many clients.
ERR_CERT_COMMON_NAME_INVALID / NET::ERR_CERT_COMMON_NAME_INVALID
What it means: The domain you're connecting to doesn't match any name in the certificate. Despite the error mentioning "Common Name," modern clients check the Subject Alternative Name (SAN) extension, not the CN field.
Common causes:
- Certificate issued for
www.example.combut you're visitingexample.com(or vice versa) - Certificate doesn't include the subdomain you're accessing
- Wildcard cert (
*.example.com) doesn't cover the bare domainexample.com - You're hitting the server by IP address but the cert only has domain names
- Reverse proxy is serving the wrong certificate (SNI misconfiguration)
How to fix:
Check what names the certificate covers:
openssl x509 -in yoursite.crt -noout -text | grep -A1 "Subject Alternative Name"
Output:
X509v3 Subject Alternative Name:
DNS:example.com, DNS:www.example.com
If your domain isn't listed, you need a new certificate that includes it. With certbot:
certbot certonly -d example.com -d www.example.com
For wildcard certs, remember that *.example.com covers www.example.com and api.example.com but does not cover example.com itself. You need both:
certbot certonly -d example.com -d '*.example.com' --dns-cloudflare
Paste your certificate into our decoder to quickly see all SANs without running openssl commands.
SSL_ERROR_HANDSHAKE_FAILURE_ALERT
What it means: The TLS handshake failed before any certificate validation happened. Client and server couldn't agree on a protocol version, cipher suite, or the server rejected the connection for another reason.
Common causes:
- Server only supports TLS 1.2+ but the client only speaks TLS 1.0/1.1
- No overlapping cipher suites between client and server
- Server requires client certificates (mTLS) but the client didn't present one
- SNI mismatch -- the server doesn't have a certificate for the requested hostname
- RSA key and certificate don't match
How to fix:
Test what the server supports:
# Check supported protocols
openssl s_client -connect yoursite.com:443 -tls1_2 </dev/null 2>&1 | grep "Protocol"
openssl s_client -connect yoursite.com:443 -tls1_3 </dev/null 2>&1 | grep "Protocol"
Verify your key matches your certificate:
# These two commands should output the same modulus hash
openssl x509 -noout -modulus -in yoursite.crt | openssl md5
openssl rsa -noout -modulus -in yoursite.key | openssl md5
If the hashes don't match, you're using the wrong private key. Re-issue the certificate with the correct key, or find the key that matches.
For cipher suite issues in nginx:
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
unable to get local issuer certificate
What it means: OpenSSL cannot find the issuer of the last certificate in the chain in its local trust store. This is OpenSSL's way of saying "the chain is incomplete."
Common causes:
- Missing intermediate certificate in the server's chain
- System trust store is empty, outdated, or not found
- Custom
CAfileorCApathpointed at the wrong location
How to fix:
First, check if it's a server-side or client-side issue:
# Test against a known-good site
openssl s_client -connect google.com:443 </dev/null 2>&1 | grep "Verify return"
If google.com also fails, your local trust store is the problem:
# Debian/Ubuntu
apt install ca-certificates
update-ca-certificates
# RHEL/CentOS
yum install ca-certificates
update-ca-trust
If only your site fails, the server isn't sending intermediates. See the fix for ERR_CERT_AUTHORITY_INVALID above.
You can also specify a CA bundle explicitly:
openssl s_client -connect yoursite.com:443 -CAfile /etc/ssl/certs/ca-certificates.crt </dev/null
certificate verify failed
What it means: This is the generic Python/Ruby/Go error when any part of certificate validation fails. It wraps all the specific OpenSSL errors into one unhelpful message.
Common causes:
- Any of the above issues (expired cert, bad chain, hostname mismatch)
- Python
requestslibrary can't find the system CA bundle - Application deployed in a minimal Docker container with no CA certificates
How to fix:
Get the real error:
python3 -c "
import ssl, socket
ctx = ssl.create_default_context()
with ctx.wrap_socket(socket.socket(), server_hostname='yoursite.com') as s:
s.connect(('yoursite.com', 443))
print(s.getpeercert())
"
The traceback will show the specific OpenSSL error code.
For Docker containers, add CA certificates to your image:
# Alpine
RUN apk add --no-cache ca-certificates
# Debian
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
For Python specifically:
pip install certifi
python -c "import certifi; print(certifi.where())"
The requests library uses certifi automatically. If your cert chain is correct and certifi is installed, it should work.
DEPTH_ZERO_SELF_SIGNED_CERT
What it means: The server presented a self-signed certificate (depth zero means the leaf certificate itself is the issue). The certificate was signed by its own key, not by any CA.
Common causes:
- Development/test certificate used in production
- Default self-signed cert from a fresh server installation (Apache's
snakeoilcert, nginx default) - Reverse proxy forwarding to a backend on HTTPS with a self-signed cert
How to fix:
For production, replace it with a real certificate. Let's Encrypt is free:
certbot certonly --nginx -d yoursite.com
For development or internal services where self-signed is intentional, add the certificate to the client's trust store (see ERR_CERT_AUTHORITY_INVALID above). Or better yet, use a proper internal CA -- tools like step-ca make this straightforward.
For backend services behind a reverse proxy, terminate TLS at the proxy and use plain HTTP between the proxy and backend on the internal network. There's no need for self-signed certs on localhost connections.
Debugging checklist
When you hit any SSL error, run through this sequence:
# 1. See what the server sends
openssl s_client -connect yoursite.com:443 -servername yoursite.com </dev/null 2>&1
# 2. Check certificate dates
openssl s_client -connect yoursite.com:443 </dev/null 2>&1 | \
openssl x509 -noout -dates
# 3. Check SANs
openssl s_client -connect yoursite.com:443 </dev/null 2>&1 | \
openssl x509 -noout -text | grep -A1 "Subject Alternative Name"
# 4. Verify key matches cert
openssl x509 -noout -modulus -in cert.pem | openssl md5
openssl rsa -noout -modulus -in key.pem | openssl md5
# 5. Verify the full chain
openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt fullchain.pem
Or skip the command line entirely: paste your PEM-encoded certificate into our decoder and get all the details in a readable format.