Introduction
When you’re the DevOps lead for a high‑traffic web service, TLS isn’t just a checkbox – it’s a performance and security cornerstone. Mis‑configured TLS can add latency, expose weak ciphers, or even open the door to downgrade attacks. This tutorial walks you through seven practical steps to lock down Nginx’s TLS stack while keeping the request‑to‑response time razor‑sharp.
1. Use Modern TLS Versions Only
Older protocol versions (SSLv2, SSLv3, TLS 1.0/1.1) are riddled with known vulnerabilities. In your nginx.conf
set the ssl_protocols
directive to the latest stable versions:
ssl_protocols TLSv1.2 TLSv1.3;
TLS 1.3 brings a 30‑40 % reduction in handshake latency and eliminates many legacy cipher suites.
2. Curate a Strong Cipher Suite
A well‑chosen cipher list prevents weak algorithms like RC4 or 3DES. The following list is vetted by Mozilla’s Intermediate configuration and works well with most browsers:
ssl_ciphers \
'TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256' \
'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 on;
-
ssl_prefer_server_ciphers on;
forces the server’s ordering, eliminating client‑side cipher manipulation.
3. Enable HTTP/2 and Server Push (Optional)
HTTP/2 works hand‑in‑hand with TLS and can shave milliseconds off TTFB. Turn it on per‑site:
listen 443 ssl http2;
If you have critical assets (e.g., CSS above the fold), consider http2_push_preload on;
to proactively push them.
4. Harden Header Security
Security‑focused response headers protect against click‑jacking, MIME sniffing, and XSS. Add them once in the http
block:
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options SAMEORIGIN always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
The Strict-Transport-Security
header forces browsers to use HTTPS for a year and signals inclusion in the Chrome preload list.
5. Configure OCSP Stapling
OCSP stapling reduces the round‑trip required for certificate revocation checks. Place these directives inside the server
block:
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
If the resolver fails, Nginx will fall back to the client‑side OCSP request, preserving availability.
6. Compress Wisely: Gzip + Brotli
Compression cuts payload size, but you must avoid compressing already‑compressed assets (e.g., JPEG, MP4). Enable both Gzip and Brotli for maximum compatibility:
# Gzip (fallback for older browsers)
gzip on;
gzip_types text/plain text/css application/json application/javascript;
gzip_vary on;
# Brotli (preferred when client supports it)
brotli on;
brotli_comp_level 6;
brotli_types text/plain text/css application/json application/javascript;
Brotli typically yields a 20‑30 % size reduction over Gzip, improving perceived speed on modern browsers.
7. Guard Against Brute‑Force TLS Handshakes with Fail2Ban
Even with perfect TLS, attackers can flood your server with malformed handshakes. A lightweight Fail2Ban jail can throttle abusive IPs:
[nginx-https]
enabled = true
filter = nginx-https
logpath = /var/log/nginx/error.log
maxretry = 5
bantime = 3600
Create a matching filter (/etc/fail2ban/filter.d/nginx-https.conf
):
[Definition]
failregex = .*client sent TLS alert.*
.*SSL_do_handshake() failed.*
Reload Fail2Ban and you’ll start seeing offending IPs blocked after a handful of failed handshakes.
Putting It All Together
A minimal, production‑ready server block might look like this:
server {
listen 443 ssl http2;
server_name example.com www.example.com;
# TLS basics
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256';
ssl_prefer_server_ciphers on;
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 8.8.8.8 valid=300s;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options SAMEORIGIN always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Compression
gzip on;
gzip_vary on;
gzip_types text/plain text/css application/json application/javascript;
brotli on;
brotli_comp_level 6;
brotli_types text/plain text/css application/json application/javascript;
# Root & static files
root /var/www/example.com/html;
index index.html index.htm;
}
Deploy this config, run nginx -t && systemctl reload nginx
, and monitor the Nginx error log for any handshake failures.
Monitoring & Ongoing Maintenance
-
Metrics: Hook into Prometheus with the
nginx_exporter
to watch TLS handshake latency and 4xx/5xx rates. -
Certificate Renewal: Automate with Certbot’s
--deploy-hook
to reload Nginx after renewal. -
Cipher Audits: Run
sslscan
ortestssl.sh
weekly to verify you haven’t unintentionally re‑enabled weak ciphers. -
Fail2Ban Tuning: Adjust
maxretry
andbantime
based on traffic patterns; a false positive can block legitimate users.
Conclusion
Hardening Nginx TLS isn’t a one‑off task; it’s a continuous loop of configuration, testing, and monitoring. By following these seven tips you’ll enjoy a tighter security posture, lower latency, and happier users. For deeper dives into Nginx performance and security best practices, check out the resources at https://lacidaweb.com.
Top comments (0)