SSL/TLS Error Fixes -- How to Fix Common SSL/TLS Errors
SSL/TLS errors like certificate expired and hostname mismatch break HTTPS connections instantly -- this guide shows you how to diagnose and fix the most common SSL certificate errors with command-line tools and server configuration fixes.
What You'll Learn
Why It Matters
SSL/TLS errors block users from accessing your website, trigger browser security warnings, and erode trust. Fixing them quickly is essential for any production web service.
Real-World Use
When your e-commerce site shows "Your connection is not private" or an API client fails with certificate verify failed, applying the correct SSL fix restores secure communication immediately.
Common SSL/TLS Errors Table
| Error Message | Cause | Fix |
|---|---|---|
| certificate has expired | SSL certificate past its validity date | Renew the certificate with your CA or Let's Encrypt |
| Hostname mismatch | Certificate CN/SAN does not match domain | Re-issue certificate with correct domain names |
| self-signed certificate in chain | Using a self-signed certificate without trust | Add the CA certificate to the trust store or use a public CA |
| SSL: CERTIFICATE_VERIFY_FAILED | Client does not trust the server certificate | Install the CA bundle or disable verification (dev only) |
| handshake failure | Protocol version or cipher mismatch | Enable TLS 1.2+ and update cipher suites on both sides |
| certificate not yet valid | System date is wrong or certificate future-dated | Sync system clock with NTP or re-issue certificate with correct dates |
| unable to get local issuer certificate | Intermediate CA certificate missing from chain | Include full certificate chain in server configuration |
Step-by-Step Fixes
Fix 1: Certificate Expired
# bad -- check certificate expiry
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -dates
# notAfter=Jun 20 12:00:00 2025 GMT (EXPIRED)
# fixed -- renew the certificate with certbot (Let's Encrypt)
sudo certbot renew --nginx
# Or force renewal
sudo certbot certonly --force-renewal -d example.com
# Verify the new expiry date
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -dates
# notAfter=Jun 22 12:00:00 2026 GMT
Expected output:
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Renewing an existing certificate for example.com
Successfully renewed certificate for example.com
Fix 2: Hostname Mismatch
# bad -- check certificate CN and SANs
openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -text | grep -A1 "Subject: CN"
# Subject: CN = wrong-domain.com
# X509v3 Subject Alternative Name:
# DNS:wrong-domain.com
# fixed -- re-issue with correct domain
sudo certbot certonly --nginx -d example.com -d www.example.com
# Verify the SANs now include your domain
openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -text | grep -A1 "Subject Alternative Name"
# DNS:example.com, DNS:www.example.com
Expected output:
Requesting a certificate for example.com and www.example.com
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/example.com/fullchain.pem
Fix 3: Self-Signed Certificate Not Trusted
# bad -- curl rejects self-signed cert
curl https://internal-api.example.com
# curl: (60) SSL certificate problem: self-signed certificate
# fixed -- add self-signed CA to trust store
# Option 1: Add the CA cert to system trust store (Linux)
sudo cp ca-cert.pem /usr/local/share/ca-certificates/internal-ca.crt
sudo update-ca-certificates
# Option 2: Use curl with the custom CA
curl --cacert /path/to/ca-cert.pem https://internal-api.example.com
# Option 3: Ignore verification (development only)
curl -k https://internal-api.example.com
Expected output:
{"status": "ok"} # successful HTTPS request
Fix 4: Missing Intermediate Certificate
# bad -- verify the chain
openssl s_client -connect example.com:443 -servername example.com -showcerts 2>/dev/null
# depth=0: CN = example.com (server cert only, incomplete chain)
# verify error: unable to get local issuer certificate
# fixed -- include full chain in server config
# Nginx: ssl_trusted_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
# Apache: SSLCertificateChainFile /etc/letsencrypt/live/example.com/chain.pem
# Verify the complete chain is served
openssl s_client -connect example.com:443 -servername example.com -showcerts 2>/dev/null | openssl crl2pkcs7 -nocrl -certfile /dev/stdin | openssl pkcs7 -print_certs -text | grep "Subject:"
Expected output:
subject=CN = example.com
subject=C = US, O = Let's Encrypt, CN = R3
subject=O = Internet Security Research Group, CN = ISRG Root X1
Fix 5: Handshake Failure
# bad -- check supported protocols
openssl s_client -connect example.com:443 -tls1_1 -servername example.com 2>/dev/null
# 140735550713408:error:1409442E:SSL routines:ssl3_read_bytes:tlsv1 alert protocol version
# fixed -- update server to support TLS 1.2 minimum
# Nginx configuration
# ssl_protocols TLSv1.2 TLSv1.3;
# ssl_ciphers HIGH:!aNULL:!MD5;
# Restart Nginx and verify
sudo nginx -t && sudo systemctl reload nginx
openssl s_client -connect example.com:443 -tls1_2 -servername example.com 2>/dev/null
Expected output:
CONNECTED(00000003)
---
SSL handshake has read 4528 bytes and written 415 bytes
---
New, TLSv1.2, Cipher is ECDHE-RSA-AES128-GCM-SHA256
SSL/TLS Error Diagnosis Flowchart
flowchart TD
A[SSL/TLS Error] --> B{Error Type?}
B -->|Certificate Expired| C[Check expiry date]
C --> D[Renew with certbot or CA]
B -->|Hostname Mismatch| E[Check CN and SANs]
E --> F[Re-issue with correct domain]
B -->|Self-Signed Not Trusted| G[Check trust store]
G --> H[Add CA to system trust or use --cacert]
B -->|Missing Intermediate| I[Check certificate chain]
I --> J[Include fullchain.pem in config]
B -->|Handshake Failure| K[Check protocol version]
K --> L[Enable TLS 1.2+ and modern ciphers]
B -->|Date Invalid| M[Check system clock]
M --> N[Sync with NTP]
D --> O[SSL Working]
F --> O
H --> O
J --> O
L --> O
N --> O
Prevention Tips
- Set up automatic certificate renewal with certbot and systemd timers
- Monitor certificate expiry dates with tools like
certwatchor UptimeRobot - Always include the full certificate chain (server cert + intermediates + root)
- Use TLS 1.2 minimum and disable SSLv3, TLSv1.0, and TLSv1.1
- Test your SSL configuration with
ssllabs.com/ssltestortestssl.sh - Keep system clocks synchronized with NTP to avoid certificate validity errors
- Use a certificate transparency monitor to get alerts before certificates expire
Practice Questions
What does "SSL certificate has expired" mean and how do you fix it? Answer: The certificate's validity period has passed. Renew it with your certificate authority using certbot or your CA's renewal process.
How do you fix a hostname mismatch SSL error? Answer: Re-issue the certificate with the correct domain names in the Subject Alternative Name (SAN) field. The certificate's CN or SAN must match the domain users visit.
What causes an SSL handshake failure? Answer: The client and server could not agree on a protocol version or cipher suite. Enable TLS 1.2+ on the server and update cipher suites to modern standards.
How do you fix "unable to get local issuer certificate"? Answer: The server is not sending the intermediate CA certificate. Configure the server to use the full certificate chain (fullchain.pem) instead of just the server certificate.
Challenge: Write a bash script that checks all domains in a file for SSL certificate expiry, warns if expiry is within 30 days, and shows the number of days remaining for each domain. Answer:
#!/bin/bash # usage: ./ssl-check.sh domains.txt DOMAINS_FILE="${1:-domains.txt}" WARN_DAYS=30 while IFS= read -r domain; do [ -z "$domain" ] && continue expiry=$(echo | openssl s_client -connect "$domain:443" -servername "$domain" 2>/dev/null | openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2) if [ -z "$expiry" ]; then echo "$domain: FAILED to connect" continue fi expiry_epoch=$(date -d "$expiry" +%s) now_epoch=$(date +%s) days_left=$(( (expiry_epoch - now_epoch) / 86400 )) if [ "$days_left" -lt 0 ]; then echo "$domain: EXPIRED ($(( -days_left )) days ago)" elif [ "$days_left" -lt "$WARN_DAYS" ]; then echo "$domain: WARNING - $days_left days remaining" else echo "$domain: OK - $days_left days remaining" fi done < "$DOMAINS_FILE"
Quick Reference
| Error | Cause | Quick Fix |
|---|---|---|
| Certificate expired | Past validity date | sudo certbot renew |
| Hostname mismatch | CN/SAN does not match domain | Re-issue cert with correct domain |
| Self-signed not trusted | Not in trust store | Add CA to system trust store |
| Missing intermediate | Incomplete cert chain | Use fullchain.pem in config |
| Handshake failure | Protocol/cipher mismatch | Enable TLS 1.2+ and modern ciphers |
| Certificate not yet valid | Wrong system date | sudo ntpdate pool.ntp.org |
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro