Common SSL/TLS Errors and What They Actually Mean

A developer's reference to the most common SSL/TLS error messages. Plain English explanations, real causes, and concrete fixes for ERR_CERT_AUTHORITY_INVALID, ERR_CERT_DATE_INVALID, handshake failures, and more.


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.com but you're visiting example.com (or vice versa)
  • Certificate doesn't include the subdomain you're accessing
  • Wildcard cert (*.example.com) doesn't cover the bare domain example.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 CAfile or CApath pointed 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 requests library 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 snakeoil cert, 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.


More in Gotchas