🔧 Network Troubleshooting 9 मिनट पढ़ें

SSL Certificate Errors: Causes and Solutions

Understand and fix SSL certificate errors including expired certificates, chain validation failures, self-signed warnings, mixed content issues, and HSTS problems.

Common Browser Warnings

SSL/TLS errors manifest as browser warnings that block access to a site. Each error code points to a specific cause:

Browser Error Meaning
ERR_CERT_DATE_INVALID Certificate expired or not yet valid
ERR_CERT_AUTHORITY_INVALID Certificate signed by untrusted CA
ERR_CERT_COMMON_NAME_INVALID Certificate hostname does not match
ERR_SSL_PROTOCOL_ERROR TLS version or cipher mismatch
NET::ERR_CERT_REVOKED Certificate has been revoked
MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT Self-signed certificate
ERR_SSL_VERSION_OR_CIPHER_MISMATCH Outdated TLS version (1.0/1.1)

For site administrators, these errors translate directly into fixes. For end users, they signal caution about the site being visited.

Certificate Chain Verification

TLS certificates form a chain of trust: your certificate → one or more intermediate certificates → a root CA certificate. Browser errors often occur because intermediate certificates are missing on the server.

# Inspect certificate chain from command line
openssl s_client -connect example.com:443 -servername example.com

# Show the full chain
openssl s_client -connect example.com:443 -showcerts 2>/dev/null | openssl x509 -noout -text

# Check with a certificate chain validator
curl --verbose https://example.com 2>&1 | grep -E "SSL|certificate|chain"

# Test from multiple locations to rule out local trust store issues
# Online tool: https://www.ssllabs.com/ssltest/

Interpreting openssl output:

Verify return code: 0 (ok)           → Chain is valid
Verify return code: 2 (unable to get issuer certificate)  → Missing intermediate
Verify return code: 10 (certificate has expired)          → Expired cert
Verify return code: 18 (self signed certificate)          → Self-signed
Verify return code: 20 (unable to get local issuer certificate) → Untrusted CA

Fix: Install the complete certificate chain on your server. Your certificate authority provides the intermediate certificate. Install it in your web server config:

# Nginx: use fullchain.pem, not just cert.pem
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# Apache
SSLCertificateFile /etc/letsencrypt/live/example.com/cert.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
SSLCertificateChainFile /etc/letsencrypt/live/example.com/chain.pem

Expired Certificates

Expired certificates are the most common SSL error and entirely preventable with automated renewal.

# Check certificate expiry
openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -dates
# notBefore=Jan  1 00:00:00 2024 GMT
# notAfter=Apr  1 00:00:00 2024 GMT   ← This is the expiry date

# Check all certs in a directory
for cert in /etc/ssl/certs/*.crt; do
    echo -n "$cert: "
    openssl x509 -in "$cert" -noout -enddate 2>/dev/null
done

# Check a Let's Encrypt cert
sudo certbot certificates

Automatic renewal with Let's Encrypt (Certbot):

# Install certbot
sudo apt install certbot python3-certbot-nginx   # Debian/Ubuntu

# Issue a certificate
sudo certbot --nginx -d example.com -d www.example.com

# Test renewal (dry run)
sudo certbot renew --dry-run

# Check the renewal timer
sudo systemctl status certbot.timer

# View renewal configuration
cat /etc/letsencrypt/renewal/example.com.conf

Let's Encrypt certificates are valid for 90 days. Certbot installs a systemd timer that attempts renewal when the certificate has less than 30 days remaining. Confirm this timer is active after every server rebuild.

Self-Signed Certificate Handling

Self-signed certificates are appropriate for internal tools, development environments, and testing. Browsers show a warning because no trusted CA has vouched for the certificate.

# Generate a self-signed certificate (development only)
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem \
  -days 365 -nodes -subj "/CN=localhost" \
  -addext "subjectAltName=DNS:localhost,IP:127.0.0.1"

# Generate with Subject Alternative Names (required by modern browsers)
openssl req -x509 -newkey rsa:4096 \
  -keyout server.key -out server.crt \
  -days 3650 -nodes \
  -subj "/C=US/O=Internal/CN=internal.example.com" \
  -addext "subjectAltName=DNS:internal.example.com,DNS:*.internal.example.com"

For internal infrastructure: Use a private CA and distribute the CA root certificate to all clients. This eliminates warnings for all certificates signed by your private CA:

# Add custom CA to system trust store (Ubuntu)
sudo cp my-root-ca.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates

# Add to macOS Keychain
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain my-root-ca.crt

# Add to Windows certificate store
certutil -addstore Root my-root-ca.crt

Mixed Content Issues

HTTPS pages that load resources (images, scripts, stylesheets) over HTTP generate mixed content warnings. Modern browsers block mixed content by default.

# Find mixed content in server logs
grep '"GET http://' /var/log/nginx/access.log

# Test for mixed content
# Browser developer tools → Console → Filter for "Mixed Content"

# Common fix: use protocol-relative URLs or HTTPS explicitly
# Change: <script src="http://cdn.example.com/lib.js">
# To:     <script src="https://cdn.example.com/lib.js">
# Or:     <script src="//cdn.example.com/lib.js">   ← protocol-relative

Server-side fix — redirect all mixed content via Content Security Policy:

# Nginx: upgrade insecure requests automatically
add_header Content-Security-Policy "upgrade-insecure-requests;";

HSTS Preloading Problems

HTTP Strict Transport Security (HSTS) instructs browsers to always use HTTPS and cache this instruction. The HSTS preload list bakes this instruction into browsers before any request is made.

# Check if a domain is on the HSTS preload list
curl -s "https://hstspreload.org/api/v2/status?domain=example.com" | python3 -m json.tool

# Check the HSTS header a site sends
curl -Is https://example.com | grep -i "strict-transport-security"
# Expected: Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

HSTS problems and fixes:

Problem Cause Fix
Site unreachable after cert expiry HSTS cached in browser Clear HSTS in browser (chrome://net-internals/#hsts)
Subdomain breaks after HSTS with includeSubDomains Subdomain has no HTTPS Add HTTPS to all subdomains before enabling
Cannot remove from preload list Preload list removal takes 6-12 months Plan carefully before submitting
# Nginx: Set HSTS header (start with short max-age to test)
add_header Strict-Transport-Security "max-age=300; includeSubDomains" always;

# Only add 'preload' after confirming all subdomains support HTTPS:
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

Certificate Transparency Logs

All publicly trusted certificates are required to be logged in Certificate Transparency (CT) logs. This allows monitoring for unauthorized certificate issuance for your domain.

# Monitor CT logs for your domain
# crt.sh is a public CT log search engine
curl -s "https://crt.sh/?q=example.com&output=json" | \
  python3 -c "import sys,json; [print(c['not_before'][:10], c['common_name']) for c in json.load(sys.stdin)]"

# Set up alerts via crt.sh (subscribe to email notifications for your domain)
# Or use a commercial service like Cert Spotter or Facebook Certificate Transparency

If you find a certificate for your domain that you did not issue, revoke it immediately through the issuing CA and rotate your DNS credentials to prevent reissuance.