DEV Community

Ramer Labs
Ramer Labs

Posted on

The Ultimate Checklist for TLS Hardening on Nginx Servers

Why TLS Hardening Matters

Transport Layer Security (TLS) is the first line of defense for any web service. A mis‑configured Nginx instance can expose your site to downgrade attacks, weak cipher suites, and man‑in‑the‑middle snooping. For a DevOps lead, a solid checklist turns a vague "secure the site" task into a repeatable, auditable process.


Checklist Overview

Below is a practical, step‑by‑step checklist you can run after each Nginx deployment. Treat it as a pre‑flight routine before you flip the DNS switch.

1. Enforce Modern Protocol Versions

  • Disable SSLv2, SSLv3, TLS 1.0, and TLS 1.1.
  • Enable TLS 1.2 and TLS 1.3 (if your OpenSSL version supports it).
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
Enter fullscreen mode Exit fullscreen mode

2. Choose Strong Cipher Suites

Prefer ciphers that provide AEAD (Authenticated Encryption with Associated Data) and PFS (Perfect Forward Secrecy). A sane default list looks like this:

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";
Enter fullscreen mode Exit fullscreen mode

Avoid the !aNULL and !MD5 shortcuts; list the exact suites you trust.

3. Enable Perfect Forward Secrecy (PFS)

PFS is baked into the cipher list above, but you can double‑check by generating a strong Diffie‑Hellman parameter file:

# Generate a 4096‑bit DH params file (run once)
openssl dhparam -out /etc/nginx/dhparam.pem 4096
Enter fullscreen mode Exit fullscreen mode

Then reference it in your config:

ssl_dhparam /etc/nginx/dhparam.pem;
Enter fullscreen mode Exit fullscreen mode

4. Turn on HTTP Strict Transport Security (HSTS)

HSTS forces browsers to only use HTTPS for your domain, preventing protocol‑downgrade attacks.

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
Enter fullscreen mode Exit fullscreen mode

Remember to test the preload flag with the official Chrome list before committing.

5. Deploy OCSP Stapling

OCSP stapling reduces latency and hides the client‑side OCSP request from attackers.

ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
Enter fullscreen mode Exit fullscreen mode

6. Harden Session Tickets

If you enable TLS 1.3, session tickets are the default. Rotate the ticket key regularly:

# Generate a new ticket key (rotate weekly)
openssl rand -hex 48 > /etc/nginx/ticket.key
Enter fullscreen mode Exit fullscreen mode
ssl_session_ticket_key /etc/nginx/ticket.key;
Enter fullscreen mode Exit fullscreen mode

7. Set Secure Cookie Flags

Force cookies to be sent only over HTTPS and make them inaccessible to JavaScript.

add_header Set-Cookie "Secure; HttpOnly; SameSite=Strict" always;
Enter fullscreen mode Exit fullscreen mode

8. Automate Certificate Renewal

Use certbot (or your ACME client of choice) with a systemd timer to keep certs fresh.

# Example certbot timer (runs twice daily)
systemctl enable --now certbot.timer
Enter fullscreen mode Exit fullscreen mode

9. Test Your Configuration

Run the SSL Labs test or use testssl.sh locally:

# Quick local test
testssl.sh https://yourdomain.com
Enter fullscreen mode Exit fullscreen mode

Aim for an A+ rating and verify that only the ciphers you listed appear.

10. Log and Monitor TLS Errors

Add a dedicated error log for TLS handshake failures. This helps you spot attacks early.

error_log /var/log/nginx/tls_errors.log warn;
Enter fullscreen mode Exit fullscreen mode

Then ship the log to your central aggregator (e.g., Loki, Graylog) and set an alert on spikes.


Putting It All Together

Below is a minimal, production‑ready snippet that combines the above steps. Paste it into your server {} block and reload Nginx.

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:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256";
    ssl_dhparam         /etc/nginx/dhparam.pem;

    # HSTS & security headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
    add_header X-Content-Type-Options nosniff;
    add_header X-Frame-Options DENY;
    add_header Referrer-Policy "no-referrer-when-downgrade";
    add_header Set-Cookie "Secure; HttpOnly; SameSite=Strict" always;

    # OCSP stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 1.1.1.1 1.0.0.1 valid=300s;
    resolver_timeout 5s;

    # Session tickets
    ssl_session_ticket_key /etc/nginx/ticket.key;

    # Logging
    access_log  /var/log/nginx/access.log;
    error_log   /var/log/nginx/tls_errors.log warn;

    # Your usual location blocks go here…
}
Enter fullscreen mode Exit fullscreen mode

Reload with nginx -t && systemctl reload nginx and verify the output of openssl s_client -connect localhost:443 -tls1_3.


Final Thoughts

TLS hardening is a moving target: new cipher suites are deprecated, and browsers add support for TLS 1.3 at different speeds. Treat this checklist as a living document—review it quarterly, automate the parts you can, and keep an eye on the security bulletins for OpenSSL and Nginx.

If you’re looking for more hands‑on guidance or managed hosting that respects these hardening practices out of the box, consider checking out https://lacidaweb.com for a straightforward, developer‑friendly solution.

Top comments (0)