Complete NGINX Hardening Guide¶
This comprehensive guide covers everything you need to harden your NGINX server against common attacks and misconfigurations. Each section includes the vulnerability, the fix, and how Gixy can automatically detect the issue.
Automate Security Checks
Don't manually audit your nginx.conf—run gixy /etc/nginx/nginx.conf to catch these issues automatically. Get started →
1. Hide NGINX Version Information¶
Risk Level: Medium Attack Vector: Information disclosure helps attackers target known CVEs
By default, NGINX exposes its version number in HTTP response headers and error pages. This helps attackers identify which vulnerabilities apply to your server.
Vulnerable Configuration¶
http {
# server_tokens defaults to 'on' if not specified
server {
listen 80;
server_name example.com;
}
}
Response header: Server: nginx/1.24.0
Hardened Configuration¶
http {
server_tokens off; # Hide version in headers and error pages
server {
listen 80;
server_name example.com;
}
}
Response header: Server: nginx
Gixy Detection
Gixy's version_disclosure check automatically detects both explicit server_tokens on; and missing directives that default to version disclosure.
2. Configure Secure SSL/TLS¶
Risk Level: Critical Attack Vector: POODLE, BEAST, Sweet32, downgrade attacks
Weak SSL/TLS configuration can allow attackers to decrypt traffic or perform man-in-the-middle attacks.
Vulnerable Configuration¶
server {
listen 443 ssl;
ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2; # Legacy protocols
ssl_ciphers ALL; # Includes weak ciphers
}
Hardened Configuration (Mozilla Intermediate)¶
server {
listen 443 ssl http2;
server_name example.com;
# Modern protocols only
ssl_protocols TLSv1.2 TLSv1.3;
# Strong cipher suites (Mozilla Intermediate)
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
}
Gixy Detection
Gixy's weak_ssl_tls check detects insecure protocols (SSLv2, SSLv3, TLSv1.0, TLSv1.1) and weak cipher suites (RC4, DES, 3DES, EXPORT, NULL).
3. Enable HTTP Strict Transport Security (HSTS)¶
Risk Level: High Attack Vector: SSL stripping, downgrade attacks
HSTS tells browsers to always use HTTPS, preventing SSL stripping attacks.
Vulnerable Configuration¶
server {
listen 443 ssl;
# Missing HSTS header - vulnerable to SSL stripping
}
Hardened Configuration¶
server {
listen 443 ssl http2;
# HSTS with 1 year max-age and subdomain inclusion
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
}
HSTS Considerations
- Start with a short
max-age(e.g., 300) during testing - Only add
preloadif you're ready to submit to the HSTS preload list - Ensure ALL subdomains support HTTPS before using
includeSubDomains
Gixy Detection
Gixy's hsts_header check detects missing or misconfigured HSTS headers on HTTPS servers.
4. Add Security Headers¶
Risk Level: Medium-High Attack Vector: XSS, clickjacking, MIME sniffing attacks
Security headers provide defense-in-depth against various client-side attacks.
Hardened Configuration¶
server {
listen 443 ssl http2;
# Prevent clickjacking
add_header X-Frame-Options "SAMEORIGIN" always;
# Prevent MIME type sniffing
add_header X-Content-Type-Options "nosniff" always;
# XSS Protection (legacy browsers)
add_header X-XSS-Protection "1; mode=block" always;
# Referrer policy
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Content Security Policy (customize for your app)
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline';" always;
# Permissions Policy
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
}
Header Inheritance Warning
NGINX's add_header directives in child blocks completely override parent block headers. If you add any header in a location block, you must re-add ALL security headers.
Gixy Detection
Gixy's add_header_redefinition check detects when child blocks accidentally clear security headers defined in parent blocks.
5. Prevent Host Header Spoofing¶
Risk Level: High Attack Vector: Cache poisoning, password reset poisoning, SSRF
Without a default server that rejects unknown hosts, attackers can send requests with arbitrary Host headers.
Vulnerable Configuration¶
server {
listen 80;
server_name example.com;
# First server block becomes default - accepts any Host header
}
Hardened Configuration¶
# Default server that rejects unknown hosts
server {
listen 80 default_server;
listen 443 ssl default_server;
server_name _;
ssl_certificate /path/to/dummy.crt;
ssl_certificate_key /path/to/dummy.key;
return 444; # Close connection without response
}
# Your actual server
server {
listen 80;
listen 443 ssl;
server_name example.com www.example.com;
# ... your config
}
Gixy Detection
Gixy's host_spoofing and default_server_flag checks detect missing default server configurations and potential host header attacks.
6. Implement Access Control¶
Risk Level: High Attack Vector: Unauthorized access, information disclosure
Restrict access to sensitive locations and always use complete allow/deny rules.
Vulnerable Configuration¶
location /admin {
allow 192.168.1.0/24;
# Missing 'deny all' - other IPs can still access!
}
Hardened Configuration¶
# Restrict admin area
location /admin {
allow 10.0.0.0/8;
allow 192.168.1.0/24;
deny all; # Always end with deny all
}
# Block access to sensitive files
location ~ /\. {
deny all;
}
location ~* \.(git|svn|env|htaccess|htpasswd)$ {
deny all;
}
# Block access to backup files
location ~* \.(bak|backup|old|orig|save|swp|tmp)$ {
deny all;
}
Gixy Detection
Gixy's allow_without_deny check detects incomplete access control rules that don't end with deny all.
7. Prevent Path Traversal with Alias¶
Risk Level: Critical Attack Vector: Directory traversal, arbitrary file read
The alias directive is prone to path traversal vulnerabilities when the location doesn't end with /.
Vulnerable Configuration¶
location /static {
alias /var/www/static/; # VULNERABLE: missing trailing slash in location
}
Attack: GET /static../etc/passwd → reads /var/www/static/../etc/passwd → /var/www/etc/passwd
Hardened Configuration¶
# Option 1: Add trailing slash to location
location /static/ {
alias /var/www/static/;
}
# Option 2: Use root instead of alias
location /static/ {
root /var/www;
}
Gixy Detection
Gixy's alias_traversal check detects alias configurations vulnerable to path traversal attacks.
8. Secure Proxy Configurations¶
Risk Level: Critical Attack Vector: SSRF, HTTP request smuggling
Improperly configured reverse proxies can allow Server-Side Request Forgery (SSRF) attacks.
Vulnerable Configuration¶
# SSRF vulnerability - attacker controls proxy destination
location ~ /proxy/(.*)/(.*)/(.*)$ {
proxy_pass $1://$2/$3;
}
Hardened Configuration¶
# Hardcoded upstream - no user control
upstream backend {
server 127.0.0.1:8080;
}
location /api/ {
proxy_pass http://backend;
proxy_http_version 1.1;
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;
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
Gixy Detection
Gixy's ssrf check detects proxy configurations where user input can control the destination.
9. Rate Limiting and DDoS Protection¶
Risk Level: Medium Attack Vector: DoS, brute force, resource exhaustion
Implement rate limiting to protect against abuse and denial-of-service attacks.
Hardened Configuration¶
http {
# Define rate limit zones
limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
limit_conn_zone $binary_remote_addr zone=addr:10m;
# Limit request body size
client_max_body_size 10m;
client_body_buffer_size 128k;
# Limit header size
large_client_header_buffers 4 16k;
server {
# General rate limiting
limit_req zone=general burst=20 nodelay;
limit_conn addr 10;
# Stricter limits for login endpoints
location /login {
limit_req zone=login burst=5 nodelay;
}
}
}
10. Logging and Monitoring¶
Risk Level: Medium Attack Vector: Missed attack detection, compliance failures
Proper logging is essential for security monitoring and incident response.
Vulnerable Configuration¶
http {
error_log off; # DANGEROUS: No error logging
}
Hardened Configuration¶
http {
# Structured logging for security analysis
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;
error_log /var/log/nginx/error.log warn;
server {
# Log 4xx/5xx responses for security monitoring
access_log /var/log/nginx/security.log security if=$loggable;
}
}
# Define loggable variable
map $status $loggable {
~^[23] 0;
default 1;
}
Gixy Detection
Gixy's error_log_off check detects dangerous configurations that disable error logging.
11. Disable Unnecessary Modules¶
Risk Level: Low-Medium Attack Vector: Reduced attack surface
Compile NGINX with only the modules you need:
./configure \
--without-http_autoindex_module \
--without-http_ssi_module \
--without-http_userid_module \
--without-http_auth_basic_module \ # Only if not used
--without-http_mirror_module
For pre-built packages, disable at runtime where possible:
autoindex off; # Disable directory listing
ssi off; # Disable SSI if not needed
12. File Permissions and Ownership¶
Risk Level: Medium Attack Vector: Privilege escalation, unauthorized modification
# Set proper ownership
chown -R root:root /etc/nginx
chown -R www-data:www-data /var/log/nginx
# Restrict configuration access
chmod 640 /etc/nginx/nginx.conf
chmod 750 /etc/nginx/conf.d
chmod 640 /etc/nginx/conf.d/*
# Protect SSL certificates
chmod 600 /etc/nginx/ssl/*.key
chmod 644 /etc/nginx/ssl/*.crt
Complete Hardened Configuration Template¶
Here's a complete nginx.conf incorporating all the hardening measures above:
user www-data;
worker_processes auto;
worker_rlimit_nofile 65535;
pid /run/nginx.pid;
events {
worker_connections 4096;
multi_accept on;
use epoll;
}
http {
# Basic settings
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# Hide version
server_tokens off;
# MIME types
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Rate limiting zones
limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
limit_conn_zone $binary_remote_addr zone=addr:10m;
# Request size limits
client_max_body_size 10m;
client_body_buffer_size 128k;
large_client_header_buffers 4 16k;
# SSL settings
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 off;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
# Logging
log_format security '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"';
access_log /var/log/nginx/access.log security;
error_log /var/log/nginx/error.log warn;
# Gzip compression
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript application/xml;
# Default server to reject unknown hosts
server {
listen 80 default_server;
listen 443 ssl default_server;
server_name _;
ssl_certificate /etc/nginx/ssl/dummy.crt;
ssl_certificate_key /etc/nginx/ssl/dummy.key;
return 444;
}
# Include site configurations
include /etc/nginx/conf.d/*.conf;
}
Verify Your Configuration with Gixy¶
After implementing these hardening measures, verify your configuration is secure:
# Install Gixy
pip install gixy-ng
# Scan your configuration
gixy /etc/nginx/nginx.conf
# Scan with all includes
gixy /etc/nginx/nginx.conf --config /etc/nginx/
# JSON output for CI/CD
gixy /etc/nginx/nginx.conf --format json
See the CI/CD Integration Guide to automate security checks in your deployment pipeline.
Next Steps¶
- NGINX Security Checklist - Quick reference checklist
- Security Headers Deep Dive - Detailed header configuration guide
- All Gixy Security Checks - Complete list of automated checks
- Try Gixy Online - Paste your config for instant analysis
Harden NGINX with maintained RPMs
Use NGINX Extras by GetPageSpeed for continuously updated NGINX and modules on RHEL/CentOS/Alma/Rocky. Learn more.