Nginx Security Hardening

By default, Nginx serves content securely, but modern threats demand extra layers of protection. This guide walks you through every recommended tweak: from HTTP/2 to strict TLS, HSTS, security headers, and hiding server details. Apply these changes to lock down your web server against clickjacking, downgrade attacks, information leakage, and more.

Key Goals

  • Activate HTTP/2 for multiplexing and header compression.
  • Enforce TLS 1.2 & 1.3 and strong cipher suites.
  • Enable HSTS to force HTTPS on repeat visits.
  • Set security headers (CSP, X‑Frame‑Options, X‑Content‑Type‑Options, Referrer‑Policy).
  • Hide Nginx version to reduce fingerprinting.

Why It Matters

Without hardening, attackers can exploit:

  • Protocol Downgrade: forcing clients to use weaker SSL/TLS versions.
  • Clickjacking: framing your site in malicious iframes.
  • MIME Sniffing: injesting content with incorrect MIME types.
  • Information Leakage: revealing Nginx versions & internal paths.
Proper headers and TLS settings dramatically reduce these risks.

Prerequisites

  • Nginx installed and serving your site over HTTPS (e.g. via Certbot).
  • Sudo or root access on your server.
  • Backup of your existing Nginx configuration.

Before You Begin

  1. Backup your config:
    sudo cp /etc/nginx/sites-available/default ~/nginx-default.bak
  2. Open your site config in an editor:
    sudo nano /etc/nginx/sites-available/default

Step‑by‑Step Hardening

1. Enable HTTP/2

Add http2 to your SSL listeners:


listen 443 ssl http2;
listen [::]:443 ssl http2;

2. Enforce Strong TLS Versions & Ciphers

Ensure only TLS 1.2 and 1.3 are allowed, with secure suites:


ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM';

3. Enable HSTS

Force browsers to use HTTPS for one year, including subdomains:


add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

4. Add Security Headers

Paste these inside your server { … } block:


add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self';" always;

5. Hide Nginx Version

Prevent version info leakage:

 server_tokens off;

Complete Secure Configuration

Below is a combined example for /etc/nginx/sites-available/default:


server {
    listen 80;
    listen [::]:80;
    server_name your.domain.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name your.domain.com;

    root /var/www/html;
    index index.html;

    # SSL certs (Certbot managed)
    ssl_certificate     /etc/letsencrypt/live/your.domain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/your.domain.com/privkey.pem;
    include             /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam         /etc/letsencrypt/ssl-dhparams.pem;

    # TLS settings
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_ciphers         'EECDH+AESGCM:EDH+AESGCM';

    # Security headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Frame-Options "DENY" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "no-referrer-when-downgrade" always;
    add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self';" always;

    # Hide version
    server_tokens off;

    location / {
        try_files $uri $uri/ =404;
    }
}

Verify & Reload

  1. sudo nginx -t → should report “syntax is OK”.
  2. sudo systemctl reload nginx to apply changes.
  3. Test in browser or via curl -I https://your.domain.com to confirm headers and HTTP/2.

Back to Home