TL;DR
The #1 SSL/TLS best practice is enforcing TLS 1.2+ only, which means turning off TLS 1.0 and 1.1. Certificates are free from Let's Encrypt and renew themselves, so there's no excuse left for a manual one. Turn on HSTS, use modern cipher suites, and check the result against SSL Labs. Then set an expiry alert: a lapsed certificate takes more sites down than any attack does.
"HTTPS isn't optional. An expired certificate is worse than no certificate at all, because it tells users you stopped paying attention."
Best Practice 1: Use TLS 1.2 or Higher 3 min
Old TLS versions aren't merely dated, they're broken, and a client that offers one will happily use it. Your server decides what's allowed.
| Version | Status | Recommendation |
|---|---|---|
| TLS 1.3 | Current | Enable (preferred) |
| TLS 1.2 | Acceptable | Enable (compatibility) |
| TLS 1.1 | Deprecated | Disable |
| TLS 1.0 | Deprecated | Disable |
| SSL 3.0 | Insecure | Disable |
# /etc/nginx/conf.d/ssl.conf
# Only allow TLS 1.2 and 1.3
ssl_protocols TLSv1.2 TLSv1.3;
# Modern cipher suites
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
# Prefer server ciphers
ssl_prefer_server_ciphers off; # Let client choose for TLS 1.3
# Session settings
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
Best Practice 2: Automate Certificate Management 5 min
Almost every HTTPS outage is the same outage: somebody meant to renew and didn't. Automate it once and the problem stops existing.
# Install Certbot
sudo apt install certbot python3-certbot-nginx
# Get certificate with auto-configuration
sudo certbot --nginx -d example.com -d www.example.com
# Auto-renewal is enabled by default
# Check with:
sudo systemctl status certbot.timer
# Test renewal
sudo certbot renew --dry-run
# Manual renewal (if needed)
sudo certbot renew
# Terraform: ACM certificate with auto-renewal
resource "aws_acm_certificate" "main" {
domain_name = "example.com"
subject_alternative_names = ["*.example.com"]
validation_method = "DNS"
lifecycle {
create_before_destroy = true
}
}
# DNS validation record
resource "aws_route53_record" "cert_validation" {
for_each = {
for dvo in aws_acm_certificate.main.domain_validation_options : dvo.domain_name => {
name = dvo.resource_record_name
record = dvo.resource_record_value
type = dvo.resource_record_type
}
}
zone_id = aws_route53_zone.main.zone_id
name = each.value.name
type = each.value.type
records = [each.value.record]
ttl = 60
}
Best Practice 3: Enable HSTS 2 min
A redirect still lets the first request go out over plain HTTP, which is the request an attacker on the same network wants. HSTS tells the browser never to try that again.
# Nginx
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# Apache
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
# Express.js with Helmet
import helmet from 'helmet';
app.use(helmet.hsts({
maxAge: 31536000, // 1 year
includeSubDomains: true,
preload: true,
}));
# Vercel (vercel.json)
{
"headers": [
{
"source": "/(.*)",
"headers": [
{
"key": "Strict-Transport-Security",
"value": "max-age=31536000; includeSubDomains; preload"
}
]
}
]
}
HSTS Preload Warning: Before enabling the preload directive, ensure all subdomains support HTTPS. Once your domain is in the preload list, HTTP access is permanently blocked in browsers. This cannot be easily undone.
Best Practice 4: Redirect HTTP to HTTPS 2 min
HSTS only helps visitors who've already been to your site once. The redirect is what catches everyone else.
# Nginx
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
# Redirect all HTTP to HTTPS
return 301 https://$server_name$request_uri;
}
# Apache
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# Express.js middleware
function requireHTTPS(req, res, next) {
if (req.headers['x-forwarded-proto'] !== 'https' &&
process.env.NODE_ENV === 'production') {
return res.redirect(301, `https://${req.hostname}${req.url}`);
}
next();
}
app.use(requireHTTPS);
Best Practice 5: Monitor Certificate Expiration 10 min
Auto-renewal fails quietly. A DNS change, a moved webroot, a rate limit, and the timer keeps running while nothing renews. Alert on the certificate itself, not on the renewal job.
#!/bin/bash
# Check certificate expiration
DOMAIN="example.com"
WARN_DAYS=30
EXPIRY=$(echo | openssl s_client -servername $DOMAIN -connect $DOMAIN:443 2>/dev/null | \
openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
if [ $DAYS_LEFT -lt $WARN_DAYS ]; then
echo "WARNING: Certificate for $DOMAIN expires in $DAYS_LEFT days"
# Send alert
curl -X POST "https://hooks.slack.com/..." \
-d "{\"text\": \"SSL cert for $DOMAIN expires in $DAYS_LEFT days\"}"
fi
# Prometheus/Grafana approach
# Use blackbox exporter to monitor SSL
- job_name: 'ssl'
metrics_path: /probe
params:
module: [http_2xx]
static_configs:
- targets:
- https://example.com
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- target_label: __address__
replacement: blackbox-exporter:9115
Best Practice 6: Test Your Configuration 5 min
Your config file says what you intended. Only a test says what you deployed.
- Test with SSL Labs (aim for A+ rating)
- Check for certificate chain issues
- Verify all subdomains have valid certs
- Test mixed content (HTTP resources on HTTPS pages)
- Verify HSTS is working correctly
# Check certificate details
openssl s_client -connect example.com:443 -servername example.com
# Check TLS versions supported
nmap --script ssl-enum-ciphers -p 443 example.com
# Check certificate chain
openssl s_client -connect example.com:443 -showcerts
# Test specific TLS version
openssl s_client -connect example.com:443 -tls1_2
openssl s_client -connect example.com:443 -tls1_3
# Check for common vulnerabilities
testssl.sh example.com
External Resources: For comprehensive SSL/TLS guidance, see the Let's Encrypt Documentation for free certificate automation and the OWASP Transport Layer Security Cheat Sheet for industry-standard TLS configuration recommendations.
Should I use a wildcard certificate?
Wildcard certs (*.example.com) are convenient but require careful key protection since one compromised key affects all subdomains. Use them for internal services or when you have many subdomains. For public-facing sites, individual certs are often safer.
Do I need an EV (Extended Validation) certificate?
No. Browsers no longer display EV certificates differently from standard DV (Domain Validation) certificates. A free Let's Encrypt certificate provides the same encryption. EV only matters if you need organizational identity verification for compliance.
How do I handle certificate rotation?
For auto-renewed certs (Let's Encrypt, ACM), rotation is automatic. For manual certs, deploy new certs before old ones expire, test, then remove old certs. Use certificate pinning only when absolutely necessary, as it makes rotation harder.
Further Reading
Put these practices into action with our step-by-step guides.
Test Your SSL Configuration
Check your TLS settings and certificate health.