Configure NGINX with comprehensive rate limiting zones, security headers, and DDoS protection rules to secure your web applications against malicious traffic and automated attacks.
Prerequisites
- Root or sudo access
- NGINX installed
- Basic understanding of HTTP and web security
What this solves
DDoS attacks and brute force attempts can overwhelm your web servers and compromise your applications. This tutorial shows you how to implement NGINX rate limiting with multiple protection zones, security headers, and monitoring to defend against various attack patterns including HTTP floods, slow attacks, and connection exhaustion.
Step-by-step configuration
Install NGINX with security modules
First, install NGINX and ensure it has the necessary modules for rate limiting and security headers.
sudo apt update
sudo apt install -y nginx nginx-module-http-limit-req
sudo systemctl enable nginx
Configure rate limiting zones
Create multiple rate limiting zones to handle different types of traffic patterns and attack vectors.
http {
# Basic rate limiting zone - 10 requests per second per IP
limit_req_zone $binary_remote_addr zone=basic:10m rate=10r/s;
# Strict rate limiting for login/admin pages - 2 requests per minute
limit_req_zone $binary_remote_addr zone=login:10m rate=2r/m;
# API rate limiting - 100 requests per minute per IP
limit_req_zone $binary_remote_addr zone=api:10m rate=100r/m;
# Connection limiting - max 20 connections per IP
limit_conn_zone $binary_remote_addr zone=conn_limit_per_ip:10m;
# Request body size limiting to prevent large payload attacks
client_max_body_size 10M;
# Connection timeout settings
client_body_timeout 12;
client_header_timeout 12;
send_timeout 10;
# Buffer size limits
client_body_buffer_size 16K;
client_header_buffer_size 1k;
large_client_header_buffers 2 1k;
include /etc/nginx/sites-enabled/*;
}
Configure security headers
Add comprehensive security headers to protect against XSS, clickjacking, and other web-based attacks.
# Security headers configuration
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" 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' http: https: data: blob: 'unsafe-inline'" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
Hide NGINX version
server_tokens off;
Rate limiting status codes
limit_req_status 429;
limit_conn_status 429;
Create DDoS protection configuration
Configure specific protection rules for common DDoS attack patterns and slow HTTP attacks.
# DDoS Protection Configuration
Block common attack patterns
map $http_user_agent $limit_bots {
~*(google|bing|yandex|msnbot) "";
~*(AhrefsBot|MJ12bot|DotBot) $binary_remote_addr;
~*(SemrushBot|BLEXBot|DomainStatsBot) $binary_remote_addr;
default "";
}
Rate limit suspicious bots
limit_req_zone $limit_bots zone=bots:10m rate=1r/m;
Slow HTTP attack protection
client_body_timeout 5s;
client_header_timeout 5s;
Connection flood protection
limit_conn_zone $server_name zone=perserver:10m;
Geographic blocking (example)
map $geoip_country_code $allowed_country {
default yes;
CN no; # Block China
RU no; # Block Russia
# Add more countries as needed
}
Configure virtual host with protection
Apply the rate limiting and security measures to your specific virtual hosts.
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
# Include security headers
include /etc/nginx/conf.d/security-headers.conf;
include /etc/nginx/conf.d/ddos-protection.conf;
# Apply connection limits
limit_conn conn_limit_per_ip 20;
limit_conn perserver 200;
# Basic rate limiting for all requests
limit_req zone=basic burst=20 nodelay;
# Block based on geographic location
if ($allowed_country = no) {
return 403;
}
# Block suspicious bots
limit_req zone=bots burst=2 nodelay;
# Special protection for admin/login pages
location ~ ^/(admin|login|wp-admin) {
limit_req zone=login burst=5 nodelay;
# Additional security for admin areas
deny 192.168.1.0/24; # Block internal networks if needed
allow 203.0.113.0/24; # Allow specific IP ranges
deny all;
try_files $uri $uri/ =404;
}
# API endpoint protection
location /api/ {
limit_req zone=api burst=50 nodelay;
# CORS headers for API
add_header 'Access-Control-Allow-Origin' '$http_origin';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
try_files $uri $uri/ =404;
}
# Static file caching and protection
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2|ttf|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
limit_req zone=basic burst=50 nodelay;
}
# Block access to sensitive files
location ~ /\. {
deny all;
}
location ~ ~$ {
deny all;
}
# Main location block
location / {
try_files $uri $uri/ =404;
}
# Error page customization to hide server info
error_page 403 /403.html;
error_page 404 /404.html;
error_page 429 /429.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
Create custom error pages
Configure custom error pages to avoid revealing server information during attacks.
sudo mkdir -p /var/www/html/error-pages
Too Many Requests
Too Many Requests
Please slow down and try again later.
Configure logging for monitoring
Set up detailed logging to monitor attack attempts and rate limiting effectiveness.
# Custom log format for security monitoring
log_format security_log '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'rt=$request_time uct="$upstream_connect_time" '
'uht="$upstream_header_time" urt="$upstream_response_time" '
'limit_req_status=$limit_req_status';
Security access log
access_log /var/log/nginx/security.log security_log;
Enable and test configuration
Activate the site configuration and test all protection mechanisms.
sudo ln -s /etc/nginx/sites-available/protected-site /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Set up fail2ban integration
Configure fail2ban to automatically ban IPs that trigger rate limits repeatedly.
sudo apt install -y fail2ban
[nginx-rate-limit]
enabled = true
port = http,https
logpath = /var/log/nginx/security.log
failregex = limiting requests, excess:. by zone . client:
findtime = 300
bantime = 600
maxretry = 5
sudo systemctl enable --now fail2ban
sudo systemctl status fail2ban
Verify your setup
Test your rate limiting and security configuration to ensure it's working properly.
# Check NGINX configuration
sudo nginx -t
Verify NGINX is running
sudo systemctl status nginx
Test rate limiting (run multiple times quickly)
curl -I http://example.com
Check security headers
curl -I http://example.com
Monitor rate limiting in action
sudo tail -f /var/log/nginx/security.log
Check fail2ban status
sudo fail2ban-client status nginx-rate-limit
Load testing verification
Use a simple load testing tool to verify rate limiting works correctly.
# Install Apache Bench for testing
sudo apt install -y apache2-utils # Ubuntu/Debian
sudo dnf install -y httpd-tools # AlmaLinux/Rocky
Test rate limiting - should show 429 errors
ab -n 100 -c 10 http://example.com/
Check the results in logs
sudo grep "429" /var/log/nginx/access.log
Monitor and tune protection
Create monitoring script
Set up automated monitoring to track attack attempts and system performance.
#!/bin/bash
NGINX Security Monitoring Script
LOG_FILE="/var/log/nginx/security.log"
REPORT_FILE="/var/log/nginx/security-report.txt"
echo "NGINX Security Report - $(date)" > $REPORT_FILE
echo "=====================================" >> $REPORT_FILE
Count rate limit violations in last hour
RATE_LIMITS=$(grep "$(date '+%d/%b/%Y:%H')" $LOG_FILE | grep -c "429")
echo "Rate limit violations in last hour: $RATE_LIMITS" >> $REPORT_FILE
Top attacking IPs
echo "\nTop attacking IPs:" >> $REPORT_FILE
grep "429" $LOG_FILE | awk '{print $1}' | sort | uniq -c | sort -nr | head -10 >> $REPORT_FILE
fail2ban status
echo "\nfail2ban banned IPs:" >> $REPORT_FILE
fail2ban-client status nginx-rate-limit >> $REPORT_FILE
echo "Report saved to: $REPORT_FILE"
sudo chmod +x /usr/local/bin/nginx-security-monitor.sh
Run monitoring script
sudo /usr/local/bin/nginx-security-monitor.sh
Set up automated reporting
Configure cron to run security monitoring and send reports.
# Add to cron for hourly monitoring
echo "0 root /usr/local/bin/nginx-security-monitor.sh" | sudo tee -a /etc/crontab
Performance optimization
| Setting | Purpose | Recommended Value |
|---|---|---|
| worker_processes | CPU utilization | auto (matches CPU cores) |
| worker_connections | Concurrent connections | 1024-4096 |
| keepalive_timeout | Connection reuse | 30-65 seconds |
| zone memory size | Rate limit storage | 10m per million IPs |
| burst size | Traffic spike handling | 10-50 requests |
# Performance optimization
worker_processes auto;
worker_connections 2048;
keepalive_timeout 30;
Enable gzip compression
gzip on;
gzip_comp_level 6;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
File descriptor limits
worker_rlimit_nofile 65535;
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| 429 errors for legitimate users | Rate limits too strict | Increase burst size or rate in zones |
| High memory usage | Zone size too large | Reduce zone memory allocation |
| fail2ban not working | Log path incorrect | Check logpath in jail configuration |
| Security headers not appearing | Include not loaded | Verify include directive in server block |
| Rate limiting not working | Missing zone application | Add limit_req directive to location blocks |
| Config test fails | Syntax error | Run sudo nginx -t for detailed error |
Advanced protection techniques
Implement request fingerprinting
Use advanced detection based on request patterns and user behavior.
# Advanced request fingerprinting
map $http_user_agent $suspicious_ua {
default 0;
~*python 1;
~*curl 1;
~*wget 1;
"" 1; # Empty user agent
}
Combine multiple factors for scoring
map "$suspicious_ua:$request_method" $risk_score {
default 0;
~1:POST 2;
~1:GET 1;
}
Rate limit based on risk score
limit_req_zone $binary_remote_addr zone=high_risk:10m rate=1r/s;
limit_req_zone $binary_remote_addr zone=medium_risk:10m rate=5r/s;
Configure SSL-specific protection
Add protection for HTTPS traffic and SSL-specific attacks.
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com;
# SSL Configuration
ssl_certificate /path/to/certificate.pem;
ssl_certificate_key /path/to/private-key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# SSL-specific rate limiting
limit_req zone=basic burst=10 nodelay;
# Include all security configurations
include /etc/nginx/conf.d/security-headers.conf;
include /etc/nginx/conf.d/ddos-protection.conf;
include /etc/nginx/conf.d/advanced-protection.conf;
location / {
# Apply risk-based rate limiting
if ($risk_score = 2) {
limit_req zone=high_risk burst=2 nodelay;
}
if ($risk_score = 1) {
limit_req zone=medium_risk burst=5 nodelay;
}
try_files $uri $uri/ =404;
}
}
Integration with monitoring systems
To get comprehensive monitoring of your NGINX security setup, consider integrating with monitoring platforms. You can set up Prometheus and Grafana monitoring to track rate limiting metrics and attack patterns.
Next steps
- Configure NGINX SSL termination with Certbot for complete HTTPS protection
- Set up monitoring with Prometheus and Grafana for security metrics
- Configure NGINX load balancing with health checks for high availability
- Implement NGINX WAF with ModSecurity and OWASP rules
- Configure NGINX log analysis with ELK stack for security monitoring
Running this in production?
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Default values
DOMAIN=""
BACKUP_DIR="/tmp/nginx_backup_$(date +%s)"
usage() {
echo "Usage: $0 -d domain.com"
echo " -d: Domain name for nginx configuration"
echo " -h: Show this help"
exit 1
}
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
cleanup() {
log_error "Script failed. Restoring from backup..."
if [ -d "$BACKUP_DIR" ]; then
cp -r "$BACKUP_DIR"/* /etc/nginx/ 2>/dev/null || true
systemctl reload nginx 2>/dev/null || true
fi
exit 1
}
trap cleanup ERR
while getopts "d:h" opt; do
case $opt in
d) DOMAIN="$OPTARG" ;;
h) usage ;;
*) usage ;;
esac
done
if [ -z "$DOMAIN" ]; then
log_error "Domain name is required"
usage
fi
if [ "$EUID" -ne 0 ]; then
log_error "This script must be run as root"
exit 1
fi
# Auto-detect distribution
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update"
PKG_INSTALL="apt install -y"
NGINX_CONF_DIR="/etc/nginx"
SITES_DIR="sites-available"
SITES_ENABLED="sites-enabled"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
NGINX_CONF_DIR="/etc/nginx"
SITES_DIR="conf.d"
SITES_ENABLED="conf.d"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum update -y"
PKG_INSTALL="yum install -y"
NGINX_CONF_DIR="/etc/nginx"
SITES_DIR="conf.d"
SITES_ENABLED="conf.d"
;;
*)
log_error "Unsupported distribution: $ID"
exit 1
;;
esac
else
log_error "Cannot detect distribution"
exit 1
fi
log_info "[1/8] Backing up existing nginx configuration..."
mkdir -p "$BACKUP_DIR"
if [ -d "$NGINX_CONF_DIR" ]; then
cp -r "$NGINX_CONF_DIR"/* "$BACKUP_DIR/"
fi
log_info "[2/8] Updating package repositories..."
$PKG_UPDATE
log_info "[3/8] Installing NGINX..."
$PKG_INSTALL nginx
systemctl enable nginx
log_info "[4/8] Creating rate limiting configuration..."
cat > "$NGINX_CONF_DIR/conf.d/rate-limiting.conf" << 'EOF'
# Rate limiting zones
limit_req_zone $binary_remote_addr zone=basic:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=2r/m;
limit_req_zone $binary_remote_addr zone=api:10m rate=100r/m;
limit_conn_zone $binary_remote_addr zone=conn_limit_per_ip:10m;
limit_conn_zone $server_name zone=perserver:10m;
# Request limits
client_max_body_size 10M;
client_body_timeout 12;
client_header_timeout 12;
send_timeout 10;
client_body_buffer_size 16K;
client_header_buffer_size 1k;
large_client_header_buffers 2 1k;
# Status codes for rate limiting
limit_req_status 429;
limit_conn_status 429;
EOF
log_info "[5/8] Creating security headers configuration..."
cat > "$NGINX_CONF_DIR/conf.d/security-headers.conf" << 'EOF'
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" 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' http: https: data: blob: 'unsafe-inline'" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Hide NGINX version
server_tokens off;
EOF
log_info "[6/8] Creating DDoS protection configuration..."
cat > "$NGINX_CONF_DIR/conf.d/ddos-protection.conf" << 'EOF'
# Bot detection and limiting
map $http_user_agent $limit_bots {
~*(google|bing|yandex|msnbot) "";
~*(AhrefsBot|MJ12bot|DotBot) $binary_remote_addr;
~*(SemrushBot|BLEXBot|DomainStatsBot) $binary_remote_addr;
default "";
}
limit_req_zone $limit_bots zone=bots:10m rate=1r/m;
# Slow HTTP attack protection
client_body_timeout 5s;
client_header_timeout 5s;
EOF
log_info "[7/8] Creating virtual host configuration..."
if [ "$SITES_DIR" = "sites-available" ]; then
VHOST_FILE="$NGINX_CONF_DIR/$SITES_DIR/$DOMAIN"
ENABLED_FILE="$NGINX_CONF_DIR/$SITES_ENABLED/$DOMAIN"
else
VHOST_FILE="$NGINX_CONF_DIR/$SITES_DIR/$DOMAIN.conf"
fi
cat > "$VHOST_FILE" << EOF
server {
listen 80;
listen [::]:80;
server_name $DOMAIN www.$DOMAIN;
# Include security configurations
include $NGINX_CONF_DIR/conf.d/security-headers.conf;
# Apply connection and rate limits
limit_conn conn_limit_per_ip 20;
limit_conn perserver 200;
limit_req zone=basic burst=20 nodelay;
limit_req zone=bots burst=1;
# Document root
root /var/www/html;
index index.html index.htm index.nginx-debian.html;
# Main location
location / {
try_files \$uri \$uri/ =404;
}
# API endpoints with stricter limits
location /api/ {
limit_req zone=api burst=50 nodelay;
try_files \$uri \$uri/ =404;
}
# Login/admin pages with very strict limits
location ~ ^/(login|admin|wp-admin) {
limit_req zone=login burst=2 nodelay;
try_files \$uri \$uri/ =404;
}
# Block common attack vectors
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
location ~ ~$ {
deny all;
access_log off;
log_not_found off;
}
# Error and access logs
error_log /var/log/nginx/$DOMAIN.error.log;
access_log /var/log/nginx/$DOMAIN.access.log;
}
EOF
# Enable site for Debian-based systems
if [ "$SITES_DIR" = "sites-available" ]; then
ln -sf "$VHOST_FILE" "$ENABLED_FILE"
fi
# Set proper permissions
chmod 644 "$NGINX_CONF_DIR/conf.d"/*.conf
chmod 644 "$VHOST_FILE"
chown root:root "$NGINX_CONF_DIR/conf.d"/*.conf "$VHOST_FILE"
log_info "[8/8] Testing and restarting NGINX..."
nginx -t
systemctl restart nginx
# Create basic index page if it doesn't exist
if [ ! -f /var/www/html/index.html ]; then
cat > /var/www/html/index.html << EOF
<!DOCTYPE html>
<html>
<head><title>$DOMAIN - DDoS Protected</title></head>
<body>
<h1>Welcome to $DOMAIN</h1>
<p>This server is protected against DDoS attacks with NGINX rate limiting.</p>
</body>
</html>
EOF
chmod 644 /var/www/html/index.html
fi
# Verification checks
log_info "Verifying configuration..."
if nginx -t &>/dev/null; then
log_info "✓ NGINX configuration is valid"
else
log_error "✗ NGINX configuration test failed"
exit 1
fi
if systemctl is-active nginx &>/dev/null; then
log_info "✓ NGINX is running"
else
log_error "✗ NGINX is not running"
exit 1
fi
if curl -s -o /dev/null -w "%{http_code}" "http://localhost" | grep -q "200"; then
log_info "✓ Web server is responding"
else
log_warn "⚠ Web server may not be responding correctly"
fi
log_info "Configuration completed successfully!"
log_info "Rate limiting zones configured:"
log_info " - Basic: 10 req/sec per IP"
log_info " - Login/Admin: 2 req/min per IP"
log_info " - API: 100 req/min per IP"
log_info " - Connections: 20 per IP, 200 per server"
log_info "Security headers and DDoS protection enabled"
log_info "Test your configuration: curl -I http://$DOMAIN"
Review the script before running. Execute with: bash install.sh