Introduction
Transport Layer Security (TLS) is the cornerstone of any modern web service. When you pair it with HTTP/2, you get lower latency, multiplexed streams, and better overall performance. However, a mis‑configured TLS stack can become an attack surface. This guide walks you through seven concrete steps to lock down TLS and HTTP/2 on an Nginx server that’s serving real traffic.
1. Use Modern Protocols Only
Older protocol versions (SSLv2, SSLv3, TLS 1.0/1.1) are riddled with known exploits. Force Nginx to speak TLS 1.2 and TLS 1.3 only.
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
If you need to support legacy browsers, terminate TLS at a load balancer or CDN and keep the backend strictly modern.
2. Adopt a Strong Cipher Suite
A good cipher suite balances security with performance. The following list is widely recommended by Mozilla’s SSL Configuration Generator:
ssl_ciphers \
"TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256" \
"ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384" \
"ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256";
-
Why these ciphers?
- They all provide forward secrecy (ECDHE).
- No RC4, DES, or 3DES.
- They are hardware‑accelerated on most CPUs.
3. Enable HTTP/2 and Optimize Its Settings
HTTP/2 is enabled with a single directive, but you can fine‑tune its behaviour.
listen 443 ssl http2;
# Optional tweaks
http2_max_concurrent_streams 128;
http2_keepalive_timeout 30s;
-
http2_max_concurrent_streams
caps the number of parallel streams per connection, preventing a single client from starving others. -
http2_keepalive_timeout
reduces idle connection waste.
4. Deploy OCSP Stapling
OCSP stapling saves a round‑trip for the client and hides your certificate‑authority queries from the public.
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
Make sure the resolver points to a reliable DNS server; otherwise stapling will fail and browsers may fall back to a full OCSP request.
5. Enforce HSTS (HTTP Strict Transport Security)
HSTS tells browsers to only ever talk HTTPS to your domain for a defined period.
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
-
max-age=31536000
– one year. -
includeSubDomains
– protects all sub‑domains. -
preload
– lets you submit the domain to the Chrome preload list for even stricter enforcement.
Caution: Once you enable preload
, removing it is a multi‑month process. Test carefully on a staging environment first.
6. Harden the Underlying Linux Host
TLS is only as strong as the OS it runs on.
- Firewall – block all inbound ports except 80/443.
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
- Fail2Ban – automatically ban IPs that repeatedly fail TLS handshakes or try common attacks.
[nginx-http-auth]
enabled = true
filter = nginx-http-auth
logpath = /var/log/nginx/error.log
maxretry = 5
bantime = 3600
- Automatic Security Updates – on Debian/Ubuntu, enable unattended upgrades.
sudo apt-get install unattended-upgrades
sudo dpkg-reconfigure unattended-upgrades
7. Automate Certificate Renewal
Let’s Encrypt makes free, trusted certificates easy, but you must renew them before they expire. Use certbot
with a systemd timer.
# Install certbot for Nginx
sudo apt-get install certbot python3-certbot-nginx
# Obtain a certificate (replace example.com)
sudo certbot --nginx -d example.com -d www.example.com
# Verify renewal works
sudo certbot renew --dry-run
Certbot drops a systemd timer (certbot.timer
) that runs twice daily, so you never have to think about it again.
Putting It All Together
Below is a minimal yet production‑ready Nginx server block that incorporates the previous recommendations.
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
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_prefer_server_ciphers on;
ssl_ciphers "TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384";
# OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 1.0.0.1 valid=300s;
resolver_timeout 5s;
# HSTS
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# Root & logging
root /var/www/html;
access_log /var/log/nginx/example.access.log;
error_log /var/log/nginx/example.error.log;
location / {
try_files $uri $uri/ =404;
}
}
Deploy the block, reload Nginx (sudo systemctl reload nginx
), and run a quick SSL test with Qualys SSL Labs or openssl s_client -connect example.com:443 -servername example.com
.
Conclusion
Hardening TLS and HTTP/2 on Nginx isn’t a one‑off checklist; it’s an ongoing discipline. By locking down protocols, ciphers, and the host OS, you dramatically reduce the attack surface while still reaping HTTP/2’s performance gains. Remember to monitor certificate expiry, keep your OS patched, and periodically review your cipher suite against emerging cryptographic research.
If you’re looking for a reliable partner to audit or host your hardened Nginx deployments, consider checking out https://lacidaweb.com for practical advice and managed services.
Top comments (0)