Nginx security cheatsheet.

Standard headers

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header X-XSS-Protection "0" always;            # disable legacy filter

always ensures headers on error responses too.

CSP

add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'nonce-$request_id'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https://api.example.com; frame-ancestors 'none'" always;

Tighten over time. Start with report-only:

add_header Content-Security-Policy-Report-Only "default-src 'self'; report-uri /csp-report" always;

CORS

location /api/ {
    if ($request_method = OPTIONS) {
        add_header Access-Control-Allow-Origin "https://app.example.com" always;
        add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE";
        add_header Access-Control-Allow-Headers "Content-Type, Authorization";
        add_header Access-Control-Max-Age 86400;
        return 204;
    }
    
    add_header Access-Control-Allow-Origin "https://app.example.com" always;
    proxy_pass http://app;
}

Avoid * for credentialed requests.

Hide nginx version

server_tokens off;
location ~ \.(jpg|png|gif|webp)$ {
    valid_referers none blocked example.com *.example.com;
    if ($invalid_referer) {
        return 403;
    }
}

Deny directories

location ~ /\.(?!well-known) {
    deny all;
    access_log off;
    log_not_found off;
}

Blocks .git, .env, .htaccess etc. Allows .well-known/.

Limit methods

location /api/ {
    if ($request_method !~ ^(GET|POST|PUT|DELETE|OPTIONS)$) {
        return 405;
    }
}

Request size

client_max_body_size 10m;
client_body_buffer_size 16k;
client_header_buffer_size 1k;
large_client_header_buffers 4 8k;

Slowloris protection

client_body_timeout 10s;
client_header_timeout 10s;
keepalive_timeout 65 60;
send_timeout 10s;

IP allowlist / denylist

location /admin/ {
    allow 10.0.0.0/8;
    allow 192.168.1.100;
    deny all;
}

Basic auth

location /admin/ {
    auth_basic "Admin";
    auth_basic_user_file /etc/nginx/.htpasswd;
}
htpasswd -c /etc/nginx/.htpasswd admin

auth_request

location /private/ {
    auth_request /auth;
    error_page 401 = @login;
    
    proxy_pass http://app;
}

location = /auth {
    internal;
    proxy_pass http://auth-service/verify;
    proxy_set_header X-Original-URI $request_uri;
}

Delegate auth to a service.

Geo-blocking

geo $bad_country {
    default 0;
    CN 1;
    RU 1;
}

server {
    if ($bad_country) { return 403; }
}

Requires GeoIP2 module.

ModSecurity / Coraza WAF

Modules add WAF capability:

load_module modules/ngx_http_modsecurity_module.so;

http {
    modsecurity on;
    modsecurity_rules_file /etc/nginx/modsec/main.conf;
}

OWASP Core Rule Set.

Bot detection (basic)

map $http_user_agent $is_bot {
    default 0;
    ~*(bot|crawl|spider|wget|curl) 1;
}

# Apply different rate limit

Block known bad UAs

if ($http_user_agent ~* "(zmeu|libwww-perl|nikto)") {
    return 444;
}

444: nginx-specific, closes connection without response.

Disable trace methods

if ($request_method ~ "(TRACE|DELETE)") {
    return 405;
}

SSL session abuse

ssl_session_tickets off;

Reduces TLS session resumption fingerprinting.

File extension block

location ~ \.(env|git|sql|bak|backup|swp|orig|inc|log)$ {
    deny all;
    log_not_found off;
}

proxy_buffering on (limits attack)

Default. Buffers full response before forwarding; reduces slowloris.

Robots / spider control

location = /robots.txt {
    alias /var/www/robots.txt;
    log_not_found off;
    access_log off;
}

Common mistakes

  • add_header without always → missing on error responses.
  • add_header in if block — inherits weirdly.
  • CSP unsafe-inline everywhere — defeats purpose.
  • server_tokens on → version disclosed.
  • Allowing TRACE / OPTIONS without need.

Read this next

If you want my security headers snippet bundle, it’s at rajpoot.dev .


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .