SSL/TLS configuration has changed significantly. This is the current state of best practices as of 2026 — what to enable, what to disable, and what’s coming.
Protocol versions
| Protocol | Status | Action |
|---|---|---|
| SSL 2.0, 3.0 | Broken | Disable — critical vulnerabilities (POODLE, DROWN) |
| TLS 1.0 | Deprecated (2021) | Disable — BEAST vulnerability |
| TLS 1.1 | Deprecated (2021) | Disable — no modern cipher support |
| TLS 1.2 | Secure | Enable — with AEAD ciphers only |
| TLS 1.3 | Current standard | Enable — fastest, most secure |
Nginx: ssl_protocols TLSv1.2 TLSv1.3;
Apache: SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
Cipher suites
TLS 1.3 (no configuration needed)
All 5 cipher suites are secure. Don’t try to customize — you can’t make it better.
TLS 1.2 (restrict to AEAD only)
# Nginx
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
ssl_prefer_server_ciphers off;
Rules:
- Only
ECDHE-*suites — ensures forward secrecy - Only
*-GCM-*or*-CHACHA20-*— AEAD ciphers only - No CBC ciphers — vulnerable to BEAST, Lucky13
ssl_prefer_server_ciphers off— let the client choose (modern clients pick the best option)
Certificate management
| Practice | Why |
|---|---|
| Use ECDSA P-256 keys | Smaller, faster than RSA 2048 |
| Automate renewal | 47-day validity by 2029 makes manual renewal impractical |
Serve fullchain.pem | Prevents chain of trust errors |
| Monitor certificate expiry | Set alerts for day 60 of 90 |
| Use Certificate Transparency monitoring | Catch unauthorized certificates |
| Rotate keys on renewal | Don’t reuse private keys across certificates |
Security headers
# HSTS — always use HTTPS (start with short max-age, increase later)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
# Prevent MIME-type sniffing
add_header X-Content-Type-Options "nosniff" always;
# Prevent clickjacking
add_header X-Frame-Options "SAMEORIGIN" always;
# Upgrade insecure sub-resources
add_header Content-Security-Policy "upgrade-insecure-requests" always;
Performance optimization
| Setting | Config | Benefit |
|---|---|---|
| HTTP/2 | listen 443 ssl http2; | Multiplexing, header compression |
| Session caching | ssl_session_cache shared:SSL:10m; | Avoids re-handshake for returning visitors |
| Session tickets off | ssl_session_tickets off; | Better forward secrecy |
| OCSP stapling | ssl_stapling on; | Faster cert verification, better privacy |
| Early data (0-RTT) | TLS 1.3 default | Zero-latency resumption (use cautiously) |
Complete Nginx config (production-ready)
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/ssl/fullchain.pem;
ssl_certificate_key /etc/ssl/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
root /var/www/html;
}
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
Test: Paste your domain at SSL Labs — this config should score A+.
What NOT to do
| Bad practice | Why | What to do instead |
|---|---|---|
ssl_protocols TLSv1 TLSv1.1; | Deprecated, vulnerable | TLS 1.2 + 1.3 only |
ssl_ciphers ALL; | Includes weak ciphers | Explicit ECDHE + AEAD list |
ssl_prefer_server_ciphers on; (TLS 1.3) | Unnecessary — TLS 1.3 ciphers are all secure | off |
Using cert.pem instead of fullchain.pem | Missing chain → trust errors | Always fullchain.pem for Nginx |
| Self-signed certs in production | Browser warnings, no trust | Let’s Encrypt (free) |
| Same private key for years | Limits damage containment | Rotate on each renewal |
| No HSTS | Vulnerable to downgrade attacks | Enable after testing |
max-age=0 for HSTS | Effectively disables HSTS | Start at 300, increase to 63072000 |
Frequently asked questions
How do I test my SSL configuration?
SSL Labs Server Test — enter your domain, get a detailed report with a letter grade (A-F). Check protocol support, cipher strength, chain validity, and known vulnerabilities.
What grade should I target?
A+ for production sites. This requires: TLS 1.2+ only, AEAD ciphers, valid chain, HSTS with long max-age. The config above achieves A+.
How often should I review my SSL config?
Annually, or when a new vulnerability is announced. Subscribe to Let’s Encrypt’s status page and follow the Mozilla SSL Configuration Generator for updated recommendations.
Is there a tool that generates the right config for me?
Yes — Mozilla SSL Configuration Generator. Select your server (Nginx, Apache, etc.), your server version, and it generates a recommended config. The “Modern” profile matches the practices in this article.