TL;DR
Complete guide to hardening nginx with security headers, TLS configuration, rate limiting, and access controls for production web servers.
Key facts
- Topology
- Nginx security hardening
TL;DR
Nginx's default configuration prioritises compatibility over security. A production server needs version hiding, strict security headers, modern TLS, rate limiting, and access controls. This guide covers each hardening step with copy-paste configuration blocks you can apply immediately.
Hide the server version
The Server header broadcasts your nginx version to every client, giving attackers a head start:
http {
server_tokens off;
}
Verify after reloading:
curl -I https://example.com | grep -i server
# Server: nginx
Security headers
Add these to your server block or a shared include file. Each header closes a specific attack vector:
# Prevent clickjacking
add_header X-Frame-Options "SAMEORIGIN" always;
# Stop MIME-type sniffing
add_header X-Content-Type-Options "nosniff" always;
# Control referrer information
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# HTTP Strict Transport Security — force HTTPS for 1 year including subdomains
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# Restrict browser features
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
# Content Security Policy — adjust to match your application
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'self';" always;
TLS configuration
Use TLS 1.2 and 1.3 only with strong cipher suites. Disable TLS 1.0 and 1.1 entirely:
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_session_tickets off;
Enable OCSP stapling so clients can verify your certificate without contacting the CA:
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 8.8.8.8 valid=300s;
resolver_timeout 5s;
Rate limiting
Protect against brute force and denial-of-service at the nginx level:
http {
limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=3r/m;
}
server {
location / {
limit_req zone=general burst=20 nodelay;
}
location /login {
limit_req zone=login burst=5 nodelay;
proxy_pass http://backend;
}
}
The login zone allows only 3 requests per minute per IP — enough for legitimate users but punishing for brute-force scripts.
Block bad user agents
Many scanners and bots identify themselves. Block them early:
if ($http_user_agent ~* (sqlmap|nikto|masscan|zgrab|python-requests|Go-http-client)) {
return 444;
}
Status 444 is an nginx-specific code that drops the connection without sending a response.
Prevent directory listing
Ensure autoindex is off (it is by default, but verify):
autoindex off;
Block direct IP access
If someone hits your server by IP instead of domain name, return nothing:
server {
listen 80 default_server;
listen 443 ssl default_server;
ssl_certificate /etc/ssl/certs/default.pem;
ssl_certificate_key /etc/ssl/private/default.key;
server_name _;
return 444;
}
Request size and timeout limits
Prevent oversized uploads and slow-loris attacks:
client_max_body_size 10m;
client_body_timeout 12s;
client_header_timeout 12s;
send_timeout 10s;
keepalive_timeout 65s;
Adjust client_max_body_size to match your application's actual upload requirements.
Test and reload
Always validate before reloading:
sudo nginx -t && sudo systemctl reload nginx
Verify headers are applied:
curl -I https://example.com
With Reflex
Reflex monitors your nginx configuration for security regressions — missing headers, weakened TLS settings, or accidentally removed rate limits — and alerts you when a configuration change introduces a vulnerability. See How it works.