TL;DR
The #1 SSL/TLS best practice is enforcing TLS 1.2+ only (disable TLS 1.0/1.1). Get free certificates from Let's Encrypt with auto-renewal. Enable HSTS to force HTTPS. Use modern cipher suites and test your configuration with SSL Labs. Certificate expiration is the most common cause of HTTPS outages.
"HTTPS is not optional. An expired certificate is worse than no certificate at all-it tells users you stopped caring about their security."
Best Practice 1: Use TLS 1.2 or Higher 3 min
Older TLS versions have known vulnerabilities:
| 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
Certificate expiration causes outages. Automate renewal:
# 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
HTTP Strict Transport Security prevents downgrade attacks:
# 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
Force all traffic to use HTTPS:
# 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
Set up alerts before certificates expire:
#!/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
Regularly verify your SSL/TLS setup:
- 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.