SSL/TLS certificate errors can break applications and block deployments. This guide covers all common certificate issues and their solutions.
Error: Certificate Has Expired
Symptom:
curl: (60) SSL certificate problem: certificate has expired
javax.net.ssl.SSLHandshakeException: Certificate expired
NET::ERR_CERT_DATE_INVALID
Solution 1 - Check expiration:
# Check certificate expiration
openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -dates
# Or using curl
curl -vI https://example.com 2>&1 | grep -i "expire"
# Check local certificate file
openssl x509 -enddate -noout -in /path/to/cert.pemSolution 2 - Renew with Let's Encrypt:
# Using certbot
sudo certbot renew
# Force renewal
sudo certbot renew --force-renewal
# Dry run test
sudo certbot renew --dry-run
# After renewal, restart services
sudo systemctl reload nginxSolution 3 - Auto-renewal setup:
# Cron job for auto-renewal
echo "0 0,12 * * * root certbot renew --quiet" | sudo tee /etc/cron.d/certbot
# Systemd timer (preferred)
sudo systemctl enable certbot.timer
sudo systemctl start certbot.timerError: Self-Signed Certificate Not Trusted
Symptom:
SSL certificate problem: self-signed certificate
unable to verify the first certificate
DEPTH_ZERO_SELF_SIGNED_CERT
Solution 1 - Add CA to trust store:
# Linux (Ubuntu/Debian)
sudo cp custom-ca.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates
# Linux (CentOS/RHEL)
sudo cp custom-ca.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust
# macOS
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain custom-ca.crtSolution 2 - Configure application:
# curl - specify CA bundle
curl --cacert /path/to/ca-bundle.crt https://internal-server.local
# Node.js
export NODE_EXTRA_CA_CERTS=/path/to/ca.pem
# Python requests
export REQUESTS_CA_BUNDLE=/path/to/ca-bundle.crt# Python with custom CA
import requests
response = requests.get('https://internal.local', verify='/path/to/ca.crt')
# Or disable verification (NOT for production!)
response = requests.get('https://internal.local', verify=False)Solution 3 - Docker with custom CA:
FROM python:3.12
# Add custom CA certificate
COPY custom-ca.crt /usr/local/share/ca-certificates/
RUN update-ca-certificates
# Node.js needs explicit path
ENV NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/custom-ca.crtError: Hostname Mismatch
Symptom:
SSL: CERTIFICATE_VERIFY_FAILED certificate verify failed: Hostname mismatch
The certificate is not valid for the requested host
NET::ERR_CERT_COMMON_NAME_INVALID
Cause: Certificate issued for different domain.
Solution 1 - Check certificate domains:
# View certificate SANs (Subject Alternative Names)
openssl s_client -connect example.com:443 2>/dev/null | \
openssl x509 -noout -text | grep -A1 "Subject Alternative Name"
# Full certificate details
echo | openssl s_client -connect example.com:443 2>/dev/null | \
openssl x509 -noout -textSolution 2 - Issue certificate with correct names:
# Let's Encrypt with multiple domains
sudo certbot certonly --nginx \
-d example.com \
-d www.example.com \
-d api.example.com
# Self-signed with SAN
openssl req -x509 -newkey rsa:4096 -sha256 -days 365 \
-nodes -keyout server.key -out server.crt \
-subj "/CN=example.com" \
-addext "subjectAltName=DNS:example.com,DNS:www.example.com,DNS:*.example.com"Error: Certificate Chain Incomplete
Symptom:
unable to get local issuer certificate
SSL certificate problem: unable to get issuer certificate
The certificate chain is incomplete
Solution 1 - Include intermediate certificates:
# Download intermediate cert from CA
wget https://letsencrypt.org/certs/lets-encrypt-r3.pem
# Concatenate in correct order
cat server.crt intermediate.crt > fullchain.crt
# Verify chain
openssl verify -CAfile ca-bundle.crt fullchain.crtSolution 2 - Check chain completeness:
# Online tool
# https://www.ssllabs.com/ssltest/
# CLI check
openssl s_client -connect example.com:443 -showcerts
# You should see multiple certificates in the outputSolution 3 - Nginx configuration:
server {
listen 443 ssl;
server_name example.com;
# Full chain, not just server cert
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
}Error: TLS Version Not Supported
Symptom:
SSL routines:ssl3_get_record:wrong version number
tlsv1 alert protocol version
sslv3 alert handshake failure
Solution 1 - Check supported TLS versions:
# Test TLS 1.2
openssl s_client -connect example.com:443 -tls1_2
# Test TLS 1.3
openssl s_client -connect example.com:443 -tls1_3
# See negotiated protocol
curl -v https://example.com 2>&1 | grep "SSL connection"Solution 2 - Configure server for modern TLS:
# Nginx - TLS 1.2 and 1.3 only
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;Solution 3 - Client configuration:
# Python - force TLS 1.2+
import ssl
import urllib.request
ctx = ssl.create_default_context()
ctx.minimum_version = ssl.TLSVersion.TLSv1_2
response = urllib.request.urlopen('https://example.com', context=ctx)// Node.js
const https = require('https');
const agent = new https.Agent({
minVersion: 'TLSv1.2'
});Error: Certificate Revoked
Symptom:
certificate revoked
NET::ERR_CERT_REVOKED
SSL_ERROR_REVOKED_CERT_ALERT
Solution 1 - Check revocation status:
# Get OCSP responder URL
openssl x509 -in cert.pem -noout -ocsp_uri
# Check OCSP status
openssl ocsp -issuer chain.pem -cert cert.pem \
-url http://ocsp.example.com -resp_textSolution 2 - Issue new certificate:
# Revoked certificates cannot be un-revoked
# Generate new CSR and request new certificate
openssl req -new -key server.key -out new-server.csr
# Request from CA or Let's Encrypt
sudo certbot certonly --nginx -d example.comError: Private Key Mismatch
Symptom:
SSL_CTX_use_PrivateKey_file failed
key values mismatch
(SSL: error:0B080074:x509 certificate routines:X509_check_private_key:key values mismatch)
Solution - Verify key matches certificate:
# Get modulus from certificate
openssl x509 -noout -modulus -in cert.pem | openssl md5
# Get modulus from private key
openssl rsa -noout -modulus -in key.pem | openssl md5
# These MD5 hashes must match!Regenerate matching pair if needed:
# Generate new private key
openssl genrsa -out new-server.key 4096
# Create CSR
openssl req -new -key new-server.key -out new-server.csr
# Self-sign or submit CSR to CA
openssl x509 -req -days 365 -in new-server.csr \
-signkey new-server.key -out new-server.crtCommon Service Configurations
Nginx:
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/ssl/certs/fullchain.pem;
ssl_certificate_key /etc/ssl/private/privkey.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
# HSTS
add_header Strict-Transport-Security "max-age=63072000" always;
}Apache:
<VirtualHost *:443>
ServerName example.com
SSLEngine on
SSLCertificateFile /etc/ssl/certs/server.crt
SSLCertificateKeyFile /etc/ssl/private/server.key
SSLCertificateChainFile /etc/ssl/certs/chain.crt
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
SSLHonorCipherOrder off
</VirtualHost>Quick Reference: Debug Commands
| Task | Command |
|------|---------|
| Check expiry | openssl x509 -enddate -noout -in cert.pem |
| View certificate | openssl x509 -text -noout -in cert.pem |
| Test connection | openssl s_client -connect host:443 |
| Verify chain | openssl verify -CAfile ca.crt cert.pem |
| Check key match | openssl x509 -modulus -noout -in cert.pem \| md5 |
| Download cert | echo \| openssl s_client -connect host:443 2>/dev/null \| openssl x509 > cert.pem |
SSL/TLS Security Consulting?
Certificate management at scale requires automation and expertise. Our team offers:
- Certificate lifecycle automation
- PKI infrastructure design
- Zero-trust architecture implementation
- Compliance auditing (PCI-DSS, HIPAA)