NGINX serves roughly a third of the world's websites and is the most popular reverse proxy for containerized applications. Its configuration flexibility is both its strength and its risk. A stock NGINX installation works, but it leaks information, accepts weak TLS, and lacks basic security controls.
This guide covers the configurations that transform NGINX from a functional web server into a hardened security boundary.
TLS Hardening
Disable Old Protocols
TLS 1.0 and 1.1 have known vulnerabilities and are deprecated by all major browsers. Allow only TLS 1.2 and 1.3:
ssl_protocols TLSv1.2 TLSv1.3;
Configure Strong Cipher Suites
Default cipher suites often include weak options for backward compatibility. Restrict to modern, secure ciphers:
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;
Enable OCSP Stapling
OCSP stapling lets NGINX provide certificate revocation status directly to clients, improving both performance and privacy:
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
Configure Session Security
TLS session tickets can leak session keys if the server is compromised. Disable them or rotate the ticket key regularly:
ssl_session_tickets off;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
Security Headers
HTTP Strict Transport Security
HSTS tells browsers to always use HTTPS for your domain:
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
The always parameter ensures the header is sent even on error responses.
Content Security Policy
CSP prevents cross-site scripting by controlling which resources can load on your pages:
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'" always;
Other Essential Headers
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
Information Leakage Prevention
Hide Server Version
By default, NGINX announces its version in the Server header and on error pages. Remove this information:
server_tokens off;
For NGINX Plus or with the headers-more module, you can remove the Server header entirely:
more_clear_headers Server;
Custom Error Pages
Default error pages reveal that you are running NGINX and can include version information. Replace them with custom pages:
error_page 404 /custom_404.html;
error_page 500 502 503 504 /custom_50x.html;
Restrict Sensitive Paths
Block access to hidden files, version control directories, and configuration files:
location ~ /\. {
deny all;
return 404;
}
location ~ ~$ {
deny all;
return 404;
}
Rate Limiting
Define Rate Limit Zones
Create rate limit zones based on client IP:
http {
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
}
Apply Rate Limits Per Location
location /api/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://backend;
}
location /auth/login {
limit_req zone=login burst=3;
proxy_pass http://auth_backend;
}
The login endpoint has a much stricter rate limit than general API endpoints.
Connection Limiting
In addition to request rate limiting, limit concurrent connections:
limit_conn_zone $binary_remote_addr zone=addr:10m;
server {
limit_conn addr 10;
}
Request Size and Timeout Controls
Limit Request Body Size
Prevent denial-of-service through large uploads:
client_max_body_size 10m;
Configure Timeouts
Prevent slow-loris and similar attacks:
client_body_timeout 12;
client_header_timeout 12;
keepalive_timeout 15;
send_timeout 10;
Limit Buffer Sizes
Restrict buffer sizes to prevent buffer overflow exploitation:
client_body_buffer_size 1k;
client_header_buffer_size 1k;
large_client_header_buffers 2 1k;
Access Control
IP-Based Restrictions
Restrict administrative endpoints to trusted networks:
location /admin {
allow 10.0.0.0/8;
deny all;
proxy_pass http://admin_backend;
}
Basic Authentication
For internal tools that do not have their own authentication:
location /internal {
auth_basic "Restricted Access";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://internal_backend;
}
Reverse Proxy Security
Sanitize Upstream Headers
When proxying requests, control which headers are forwarded to backends and which backend headers are returned to clients:
proxy_hide_header X-Powered-By;
proxy_hide_header X-AspNet-Version;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
Prevent SSRF Through Proxy
If NGINX proxies to user-specified URLs (which should be avoided when possible), validate and restrict the upstream addresses to prevent server-side request forgery.
Logging
Enable Detailed Access Logging
log_format security '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'$request_time $upstream_response_time '
'$ssl_protocol $ssl_cipher';
access_log /var/log/nginx/access.log security;
Enable Error Logging
error_log /var/log/nginx/error.log warn;
Forward both access and error logs to your SIEM for security monitoring and incident investigation.
How Safeguard.sh Helps
Safeguard.sh monitors the security of your NGINX deployments by scanning NGINX container images for known vulnerabilities, tracking the modules and dependencies compiled into your NGINX build, and generating SBOMs that provide complete visibility into your web server infrastructure. When a CVE affects NGINX or one of its modules, Safeguard.sh identifies every instance in your environment that needs patching.