SSL Certificate Errors: Complete Troubleshooting

SSL Certificate Errors: Complete Troubleshooting

SSL certificate errors prevent secure HTTPS connections between clients and servers. Whether you see “Your connection is not private” in a browser, SSL: CERTIFICATE_VERIFY_FAILED in Python, or curl: (60) SSL certificate problem in the terminal, this guide covers every common SSL error and how to fix it.

Common SSL Certificate Errors

ErrorMeaning
NET::ERR_CERT_DATE_INVALIDCertificate has expired
NET::ERR_CERT_AUTHORITY_INVALIDCertificate not signed by a trusted CA
NET::ERR_CERT_COMMON_NAME_INVALIDCertificate hostname does not match
SSL_ERROR_HANDSHAKE_FAILURE_ALERTTLS handshake failed
ERR_SSL_VERSION_OR_CIPHER_MISMATCHNo shared TLS version or cipher
CERTIFICATE_VERIFY_FAILEDPython cannot verify the certificate
curl: (60)cURL cannot verify the certificate
curl: (35)SSL connect error

Diagnosing SSL Errors

With cURL

# Check SSL certificate details

curl -vI https://example.com 2>&1 | grep -A 5 "SSL certificate"

Show full certificate chain

openssl s_client -connect example.com:443 -servername example.com

Check certificate expiry

echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -dates

Check certificate subject and issuer

echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -subject -issuer

Show all certificates in the chain

openssl s_client -connect example.com:443 -servername example.com -showcerts

With Python

import ssl

import socket

hostname = "example.com"

context = ssl.create_default_context()

with socket.create_connection((hostname, 443)) as sock:

with context.wrap_socket(sock, server_hostname=hostname) as ssock:

cert = ssock.getpeercert()

print(f"Subject: {cert['subject']}")

print(f"Issuer: {cert['issuer']}")

print(f"Valid from: {cert['notBefore']}")

print(f"Valid until: {cert['notAfter']}")

print(f"SANs: {cert.get('subjectAltName', [])}")

Fix 1: Expired Certificate

Symptom: NET::ERR_CERT_DATE_INVALID or certificate notAfter date is in the past.

# Check expiry

echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -enddate

Renew with Let's Encrypt / Certbot

sudo certbot renew

Or renew a specific certificate

sudo certbot certonly --nginx -d example.com -d www.example.com

Set up auto-renewal

sudo crontab -e

Add: 0 3 * certbot renew --quiet

Fix 2: Self-Signed Certificate

Symptom: NET::ERR_CERT_AUTHORITY_INVALID or unable to get local issuer certificate.

The certificate was not signed by a trusted Certificate Authority.

# For development: skip verification (NOT for production)

curl -k https://self-signed.example.com

Or specify the CA certificate

curl --cacert /path/to/ca-cert.pem https://self-signed.example.com

import requests

Skip verification (development only)

response = requests.get("https://self-signed.example.com", verify=False)

Proper fix: specify CA bundle

response = requests.get("https://self-signed.example.com", verify="/path/to/ca-cert.pem")

Production fix: Get a free certificate from Let’s Encrypt:

sudo apt install certbot python3-certbot-nginx

sudo certbot --nginx -d example.com

Fix 3: Hostname Mismatch

Symptom: NET::ERR_CERT_COMMON_NAME_INVALID — the certificate is valid but was issued for a different domain.

# Check which domains the certificate covers

echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -text | grep -A 1 "Subject Alternative Name"

Fix: Reissue the certificate to include the correct domain(s):

sudo certbot --nginx -d example.com -d www.example.com -d api.example.com

Fix 4: Incomplete Certificate Chain

Symptom: Works in browsers but fails in cURL/Python. unable to verify the first certificate.

The server is not sending the full certificate chain (missing intermediate certificates).

# Check the chain

openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | grep -E "depth|verify"

Download and combine certificates

cat your-domain.crt intermediate.crt root.crt > fullchain.pem

Nginx:

ssl_certificate /etc/ssl/fullchain.pem;    # Full chain, not just the domain cert

ssl_certificate_key /etc/ssl/private.key;

Apache:

SSLCertificateFile /etc/ssl/domain.crt

SSLCertificateKeyFile /etc/ssl/private.key

SSLCertificateChainFile /etc/ssl/intermediate.crt

Fix 5: TLS Version Mismatch

Symptom: ERR_SSL_VERSION_OR_CIPHER_MISMATCH — the client and server cannot agree on a TLS version.

# Check supported TLS versions

nmap --script ssl-enum-ciphers -p 443 example.com

Force a specific TLS version with cURL

curl --tlsv1.2 https://example.com

curl --tlsv1.3 https://example.com

Fix: Enable modern TLS versions on your server:

# Nginx

ssl_protocols TLSv1.2 TLSv1.3;

ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';

ssl_prefer_server_ciphers on;

Fix 6: Outdated CA Bundle

Symptom: Certificate is valid but the client does not trust it because its CA bundle is outdated.

# Update CA certificates (Ubuntu/Debian)

sudo apt update && sudo apt install ca-certificates

sudo update-ca-certificates

Update CA certificates (CentOS/RHEL)

sudo yum install ca-certificates

sudo update-ca-trust

Update certifi for Python

pip install --upgrade certifi

Check certifi CA bundle location

python -c "import certifi; print(certifi.where())"

SSL Errors with Proxies

When using proxies, SSL errors can occur at two points: between your client and the proxy, or between the proxy and the target server.

# Test SSL through a proxy

curl -v -x http://user:pass@proxy.example.com:8080 https://example.com

If proxy uses HTTPS (CONNECT tunnel)

curl -v --proxy-cacert /path/to/proxy-ca.pem -x https://proxy.example.com:8443 https://example.com

import requests

proxies = {

"https": "http://user:pass@proxy.example.com:8080"

}

If proxy causes SSL issues, you may need to specify CA for the proxy

response = requests.get("https://example.com", proxies=proxies, verify="/path/to/ca-bundle.pem")

Quick Troubleshooting Flowchart

  1. Is the certificate expired? Run openssl s_client and check dates. Renew with Certbot.
  2. Is it self-signed? Get a real certificate from Let’s Encrypt.
  3. Does the hostname match? Reissue the certificate with correct SANs.
  4. Is the chain complete? Use the full chain file, not just the domain cert.
  5. Is TLS version supported? Enable TLSv1.2+ on the server.
  6. Is the CA bundle outdated? Update system CA certificates.



FAQ

Should I ignore SSL errors in production?

Never. Disabling SSL verification (curl -k or verify=False) removes encryption protection and makes you vulnerable to man-in-the-middle attacks. Only use this for local development and testing.

How do I get a free SSL certificate?

Use Let’s Encrypt with Certbot. It provides free, auto-renewable certificates trusted by all major browsers.

Why does my SSL certificate work in Chrome but not in cURL?

Browsers are more forgiving and can fetch missing intermediate certificates automatically. cURL and other tools require the full certificate chain to be served by the server.

How often should I renew my SSL certificate?

Let’s Encrypt certificates expire every 90 days and should be auto-renewed. Commercial certificates typically last 1-2 years.

Scroll to Top