SSL Certificate Errors: Causes and Solutions
Embed This Widget
Add the script tag and a data attribute to embed this widget.
Embed via iframe for maximum compatibility.
<iframe src="https://ipfyi.com/iframe/guide/ssl-certificate-errors/" width="420" height="400" frameborder="0" style="border:0;border-radius:10px;max-width:100%" loading="lazy"></iframe>
Paste this URL in WordPress, Medium, or any oEmbed-compatible platform.
https://ipfyi.com/guide/ssl-certificate-errors/
Add a dynamic SVG badge to your README or docs.
[](https://ipfyi.com/guide/ssl-certificate-errors/)
Use the native HTML custom element.
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.