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_streamscaps the number of parallel streams per connection, preventing a single client from starving others. -
http2_keepalive_timeoutreduces 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)