Last month, a production site went down because the SSL certificate had expired. Not because nobody knew it was expiring — the renewal email had been going to an engineer who’d left the company six months earlier. The certificate had been manually provisioned, there was no monitoring on the expiry date, and the first sign of trouble was customers seeing “Your connection is not private” in their browsers.
This kind of incident is entirely preventable. But preventing it requires understanding how certificates actually work, not just knowing how to click “Enable SSL” in your hosting dashboard.
How TLS Works (The 60-Second Version)
When your browser connects to https://example.com, a TLS handshake happens before any data is exchanged:
1. Browser → Server: "Hello, I support TLS 1.3, these cipher suites"
2. Server → Browser: "Let's use TLS 1.3 with AES-256-GCM. Here's my certificate"
3. Browser validates the certificate:
- Is it expired?
- Does the domain match?
- Was it issued by a trusted CA?
- Is the certificate chain complete?
4. Browser and server agree on encryption keys
5. Encrypted communication begins
The certificate is what makes step 3 possible. It’s the server’s identity proof — like a digital passport issued by a trusted authority.
Anatomy of a Certificate
A certificate contains:
Subject: CN=example.com
Subject Alternative Names: example.com, www.example.com, api.example.com
Issuer: Let's Encrypt Authority X3
Valid From: 2026-01-15
Valid To: 2026-04-15
Public Key: RSA 2048 bits
Signature Algorithm: SHA-256 with RSA
Serial Number: 04:a3:b2:...
The important fields:
| Field | Why It Matters |
|---|---|
| Subject / SAN | Must match the domain the browser is connecting to |
| Issuer | Must be a Certificate Authority the browser trusts |
| Valid From / To | The certificate only works within this date range |
| Public Key | Used for the initial key exchange |
| Signature Algorithm | SHA-1 is deprecated; must be SHA-256 or better |
Inspect certificate fields: Our SSL Certificate Checker helps you analyze certificate configurations, verify fields, and understand the chain of trust — all in your browser.
Certificate Types
Domain Validation (DV)
The CA verifies you control the domain (via DNS record or HTTP challenge). This is what Let’s Encrypt provides. Sufficient for most websites and APIs.
Validation: Automated, takes seconds Cost: Free (Let’s Encrypt) to ~$10/year Browser indicator: Padlock icon
Organization Validation (OV)
The CA verifies the domain and the organization’s identity (business registration, phone verification). Shows organization name in certificate details.
Validation: 1-3 business days Cost: $50-200/year Browser indicator: Padlock icon (same as DV visually)
Extended Validation (EV)
The most thorough verification — the CA checks legal existence, physical address, and operational status. Used to show a green address bar, but modern browsers no longer distinguish EV visually.
Validation: 1-2 weeks Cost: $100-500/year Browser indicator: Padlock icon (same as DV/OV — the green bar is gone)
My recommendation: Use DV certificates (Let’s Encrypt) for everything unless your compliance requirements specifically mandate OV or EV. The encryption is identical regardless of type.
Let’s Encrypt: Free Certificates for Everyone
Let’s Encrypt issues free DV certificates with automated renewal. If you’re not using it, you should be.
Certbot Setup
# Install certbot
sudo apt install certbot python3-certbot-nginx # Debian/Ubuntu
sudo yum install certbot python3-certbot-nginx # CentOS/RHEL
# Get a certificate (Nginx)
sudo certbot --nginx -d example.com -d www.example.com
# Get a certificate (Apache)
sudo certbot --apache -d example.com -d www.example.com
# Get a certificate (standalone — for non-web servers)
sudo certbot certonly --standalone -d example.com
Automatic Renewal
Let’s Encrypt certificates expire every 90 days (intentionally short to encourage automation). Certbot sets up automatic renewal:
# Test renewal
sudo certbot renew --dry-run
# The systemd timer or cron job handles actual renewal
# Check it's active:
systemctl list-timers | grep certbot
DNS Challenge (Wildcard Certificates)
For wildcard certificates (*.example.com), you need DNS validation:
sudo certbot certonly --manual --preferred-challenges dns \
-d "*.example.com" -d example.com
Certbot will ask you to create a TXT record:
_acme-challenge.example.com. TXT "abc123..."
For automated wildcard renewal, use a DNS plugin (Cloudflare, Route 53, etc.):
sudo certbot certonly --dns-cloudflare \
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
-d "*.example.com" -d example.com
The Certificate Chain
Browsers don’t trust server certificates directly. They trust a small set of “root” CAs, and those roots sign intermediate CAs, which sign your certificate:
Root CA (trusted by browser)
└── Intermediate CA (signed by root)
└── Your certificate (signed by intermediate)
Your server must send the full chain (your cert + intermediate certs). The root is already in the browser’s trust store, so you don’t send it.
The Missing Intermediate Problem
If your server sends only your certificate without the intermediate, some browsers and clients will fail:
# What your server should send
-----BEGIN CERTIFICATE-----
(your certificate)
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
(intermediate certificate)
-----END CERTIFICATE-----
# What some misconfigured servers send
-----BEGIN CERTIFICATE-----
(your certificate only)
-----END CERTIFICATE-----
Desktop browsers sometimes fetch the missing intermediate on their own (AIA fetching), so the site “works” in Chrome but fails in:
- Mobile browsers
- API clients (curl, Python requests, Node.js)
- Older operating systems
- Monitoring tools
This is one of the most frustrating SSL bugs because it’s intermittent — it works for some people but not others.
Debugging Certificate Issues
Check Certificate from the Command Line
# Show certificate details
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -text -noout
# Check certificate expiry
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -dates
# Verify the full chain
openssl s_client -connect example.com:443 -servername example.com -showcerts
Common Certificate Errors
“NET::ERR_CERT_DATE_INVALID”
Certificate has expired. Renew it. If using Let’s Encrypt, check that auto-renewal is working (certbot renew --dry-run).
“NET::ERR_CERT_COMMON_NAME_INVALID”
The certificate doesn’t cover the domain you’re accessing. Check the SAN (Subject Alternative Name) list — the domain must be listed there. A certificate for example.com doesn’t automatically cover www.example.com.
“NET::ERR_CERT_AUTHORITY_INVALID” The browser doesn’t trust the CA that issued the certificate. Common causes:
- Self-signed certificate (fine for development, not production)
- Missing intermediate certificate
- The CA was removed from the browser’s trust store
“SSL: CERTIFICATE_VERIFY_FAILED” (Python/Node.js) Same as above, but from a non-browser client. The client’s CA bundle might be outdated:
# Python — update certifi
pip install --upgrade certifi
# Or specify the CA bundle
import requests
response = requests.get('https://example.com', verify='/path/to/ca-bundle.crt')
// Node.js — if you must (development only!)
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
// Better: specify the CA
const https = require('https');
const agent = new https.Agent({
ca: fs.readFileSync('/path/to/ca-bundle.crt'),
});
TLS Configuration Best Practices
Minimum TLS Version
# Nginx — only allow TLS 1.2 and 1.3
ssl_protocols TLSv1.2 TLSv1.3;
TLS 1.0 and 1.1 are deprecated (RFC 8996). If your server still accepts them, older clients can negotiate a weak connection. Disable them.
Strong Cipher Suites
# Nginx — modern cipher configuration
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 off;
OCSP Stapling
Normally, the browser checks if a certificate has been revoked by contacting the CA’s OCSP server. This adds latency and leaks browsing data to the CA. OCSP stapling lets your server include the revocation status, so the browser doesn’t need to ask:
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
Certificate Monitoring
Never let a certificate expire unexpectedly. Set up monitoring:
# Simple script to check expiry
DOMAIN="example.com"
EXPIRY=$(echo | openssl s_client -connect $DOMAIN:443 -servername $DOMAIN 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
DAYS_LEFT=$(( ($(date -d "$EXPIRY" +%s) - $(date +%s)) / 86400 ))
if [ $DAYS_LEFT -lt 30 ]; then
echo "WARNING: $DOMAIN certificate expires in $DAYS_LEFT days"
fi
Better yet, use a monitoring service that alerts you 30, 14, and 7 days before expiry. Certificate expiration is the most preventable production outage there is.
Self-Signed Certificates for Development
For local development, generate a self-signed certificate:
# Generate a self-signed cert valid for 365 days
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes \
-subj "/CN=localhost" \
-addext "subjectAltName=DNS:localhost,IP:127.0.0.1"
Use this for local HTTPS testing. Your browser will show a warning — that’s expected. Never use self-signed certificates in production.
For a smoother local experience, tools like mkcert create locally-trusted certificates:
# Install mkcert
brew install mkcert # macOS
mkcert -install # Add local CA to trust store
# Create certificates
mkcert localhost 127.0.0.1 ::1
# → localhost+2.pem and localhost+2-key.pem
Quick Checklist
- Use Let’s Encrypt with automated renewal for all certificates
- Certificate covers all domains and subdomains (check SAN list)
- Full certificate chain is served (intermediate + server cert)
- Minimum TLS 1.2, prefer TLS 1.3
- HSTS header is set (see HTTP Security Headers Guide)
- OCSP stapling is enabled
- Certificate expiry monitoring is in place
- CAA DNS record restricts which CAs can issue for your domain
- Auto-renewal is tested (
certbot renew --dry-run) - Certificate renewal notifications go to a team inbox, not an individual
Further Reading
- HTTP Security Headers: The Complete Checklist
- DNS Records Explained: What Every Developer Should Know
- CORS Errors Explained: Why Your Fetch Call Fails
Analyzing an SSL certificate? Our SSL Certificate Checker breaks down certificate fields, validity, and configuration in a clear format. Pair it with a DNS Lookup to verify your domain’s CAA records and nameserver configuration.