Learn to configure production-grade NGINX rate limiting and DDoS protection with multiple security zones, advanced rules, and real-time monitoring to protect your web applications from malicious traffic and resource exhaustion attacks.
Prerequisites
- Root or sudo access
- Basic NGINX knowledge
- Understanding of HTTP protocols
- Mail server for alerts (optional)
What this solves
Rate limiting and DDoS protection are critical for protecting web applications from traffic spikes, brute force attacks, and resource exhaustion. This tutorial configures NGINX with multiple rate limiting zones, adaptive security rules, and monitoring capabilities to automatically block malicious traffic while maintaining legitimate user access.
Step-by-step configuration
Update system packages and install NGINX
Start by updating your system and installing NGINX with required modules for advanced security features.
sudo apt update && sudo apt upgrade -y
sudo apt install -y nginx nginx-module-njs geoip-database
Enable NGINX security modules
Enable essential NGINX modules for enhanced security and rate limiting capabilities.
# Add at the top of nginx.conf, before events block
load_module modules/ngx_http_geoip_module.so;
load_module modules/ngx_http_realip_module.so;
load_module modules/ngx_http_limit_req_module.so;
load_module modules/ngx_http_limit_conn_module.so;
Configure rate limiting zones
Create multiple rate limiting zones for different types of traffic patterns and attack vectors.
# Rate limiting zones configuration
http {
# Define 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_req_zone $binary_remote_addr zone=api:10m rate=100r/m;
limit_req_zone $binary_remote_addr zone=search:10m rate=5r/s;
limit_req_zone $binary_remote_addr zone=upload:10m rate=1r/m;
# Connection limiting zones
limit_conn_zone $binary_remote_addr zone=conn_limit_per_ip:10m;
limit_conn_zone $server_name zone=conn_limit_per_server:10m;
# Define real IP from trusted proxies
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 172.16.0.0/12;
set_real_ip_from 192.168.0.0/16;
set_real_ip_from 203.0.113.0/24;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
}
Create GeoIP blocking configuration
Configure geographic blocking to automatically deny traffic from high-risk countries or regions.
# GeoIP configuration for geographic blocking
http {
geoip_country /usr/share/GeoIP/GeoIP.dat;
# Map countries to blocking rules
map $geoip_country_code $blocked_country {
default 0;
# Add country codes to block (examples)
CN 1; # China
RU 1; # Russia
# Add more as needed
}
# Map for allowed countries (whitelist approach)
map $geoip_country_code $allowed_country {
default 0;
US 1; # United States
CA 1; # Canada
GB 1; # United Kingdom
DE 1; # Germany
FR 1; # France
# Add your allowed countries
}
}
Configure advanced DDoS protection rules
Implement comprehensive DDoS protection with multiple layers of security checks and automatic blocking.
# Advanced DDoS protection configuration
http {
# Request size limits
client_body_buffer_size 1K;
client_header_buffer_size 1k;
client_max_body_size 1k;
large_client_header_buffers 2 1k;
# Timeout settings
client_body_timeout 10;
client_header_timeout 10;
keepalive_timeout 5 5;
send_timeout 10;
# Buffer overflow protection
server_tokens off;
# Limit request methods
map $request_method $not_allowed_method {
default 1;
GET 0;
POST 0;
HEAD 0;
}
# Block suspicious user agents
map $http_user_agent $blocked_agent {
default 0;
~*bot 1;
~*crawler 1;
~*spider 1;
~*scanner 1;
"" 1; # Empty user agent
"-" 1; # Dash user agent
}
# Block requests without proper referrer (for POST requests)
map $request_method:$http_referer $suspicious_request {
default 0;
"POST:" 1;
"POST:-" 1;
}
}
Configure server block with security rules
Apply rate limiting and security rules to your server configuration with proper error handling.
server {
listen 80;
server_name example.com www.example.com;
# Apply connection limits
limit_conn conn_limit_per_ip 10;
limit_conn conn_limit_per_server 100;
# Block based on geographic location (if using blacklist)
if ($blocked_country) {
return 403 "Access denied from your location";
}
# Block suspicious user agents
if ($blocked_agent) {
return 403 "Blocked user agent";
}
# Block non-allowed HTTP methods
if ($not_allowed_method) {
return 405 "Method not allowed";
}
# Apply general rate limiting
limit_req zone=general burst=20 nodelay;
limit_req_status 429;
# Custom error pages for rate limiting
error_page 403 /403.html;
error_page 429 /429.html;
location = /403.html {
root /var/www/html;
internal;
}
location = /429.html {
root /var/www/html;
internal;
}
# Login endpoint with strict rate limiting
location /login {
limit_req zone=login burst=3 nodelay;
# Your login handler
try_files $uri $uri/ =404;
}
# API endpoints with moderate rate limiting
location /api/ {
limit_req zone=api burst=10 nodelay;
# Your API handler
try_files $uri $uri/ =404;
}
# Search functionality with burst protection
location /search {
limit_req zone=search burst=10;
# Your search handler
try_files $uri $uri/ =404;
}
# File upload with very strict limits
location /upload {
limit_req zone=upload burst=1 nodelay;
client_max_body_size 10M;
# Your upload handler
try_files $uri $uri/ =404;
}
# Static files with relaxed limits
location ~* \.(css|js|png|jpg|jpeg|gif|svg|woff|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# Block access to sensitive files
location ~* \.(htaccess|htpasswd|ini|conf|log)$ {
deny all;
return 404;
}
# Main location block
location / {
try_files $uri $uri/ =404;
}
}
Create custom error pages
Design informative error pages for rate-limited and blocked users to improve user experience.
sudo mkdir -p /var/www/html
sudo chown -R www-data:www-data /var/www/html
Rate Limited - Too Many Requests
429 - Too Many Requests
You have exceeded the rate limit. Please wait a moment and try again.
If you believe this is an error, please contact support.
Access Denied
403 - Access Denied
Your request has been blocked by our security system.
Configure logging for security monitoring
Set up detailed logging to monitor rate limiting effectiveness and security events.
# Security-focused logging configuration
http {
# Custom log format for security analysis
log_format security_log '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'$request_time $upstream_response_time '
'$geoip_country_code $geoip_country_name '
'rate_limit: $limit_req_status';
# Separate log for blocked requests
log_format blocked_log '$remote_addr [$time_local] "$request" '
'blocked: $status reason: "$sent_http_content_type" '
'agent: "$http_user_agent" '
'country: $geoip_country_code';
# Access log with security information
access_log /var/log/nginx/access.log security_log;
# Error log for debugging
error_log /var/log/nginx/error.log warn;
}
Create monitoring script
Implement a monitoring script to track rate limiting statistics and generate alerts for unusual activity.
#!/bin/bash
NGINX Security Monitoring Script
Monitors rate limiting and generates alerts
LOG_FILE="/var/log/nginx/access.log"
ALERT_EMAIL="admin@example.com"
THRESHOLD_429=100 # Alert if more than 100 rate limit hits in 5 minutes
THRESHOLD_403=50 # Alert if more than 50 blocked requests in 5 minutes
Check rate limiting hits in last 5 minutes
RATE_LIMIT_COUNT=$(grep "$(date -d '5 minutes ago' '+%d/%b/%Y:%H:%M')" "$LOG_FILE" | grep -c " 429 ")
BLOCKED_COUNT=$(grep "$(date -d '5 minutes ago' '+%d/%b/%Y:%H:%M')" "$LOG_FILE" | grep -c " 403 ")
Generate alert if thresholds exceeded
if [ "$RATE_LIMIT_COUNT" -gt "$THRESHOLD_429" ]; then
echo "ALERT: High rate limiting activity detected - $RATE_LIMIT_COUNT hits in 5 minutes" | \
mail -s "NGINX Security Alert - Rate Limiting" "$ALERT_EMAIL"
fi
if [ "$BLOCKED_COUNT" -gt "$THRESHOLD_403" ]; then
echo "ALERT: High blocking activity detected - $BLOCKED_COUNT blocks in 5 minutes" | \
mail -s "NGINX Security Alert - Access Blocked" "$ALERT_EMAIL"
fi
Generate daily security report
if [ "$(date +%H:%M)" = "09:00" ]; then
echo "Daily NGINX Security Report - $(date)" > /tmp/security_report.txt
echo "Rate Limited Requests (429): $(grep -c " 429 " "$LOG_FILE")" >> /tmp/security_report.txt
echo "Blocked Requests (403): $(grep -c " 403 " "$LOG_FILE")" >> /tmp/security_report.txt
echo "Top Blocked IPs:" >> /tmp/security_report.txt
grep " 403 " "$LOG_FILE" | awk '{print $1}' | sort | uniq -c | sort -nr | head -10 >> /tmp/security_report.txt
mail -s "Daily NGINX Security Report" "$ALERT_EMAIL" < /tmp/security_report.txt
fi
sudo chmod +x /usr/local/bin/nginx-security-monitor.sh
sudo chown root:root /usr/local/bin/nginx-security-monitor.sh
Set up log rotation
Configure log rotation to manage disk space while retaining security logs for analysis.
/var/log/nginx/*.log {
daily
missingok
rotate 52
compress
delaycompress
notifempty
create 0644 www-data adm
sharedscripts
prerotate
if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
run-parts /etc/logrotate.d/httpd-prerotate; \
fi
endscript
postrotate
invoke-rc.d nginx rotate >/dev/null 2>&1
endscript
}
Test configuration and start NGINX
Validate your NGINX configuration and start the service with rate limiting enabled.
sudo nginx -t
sudo systemctl enable --now nginx
sudo systemctl status nginx
Configure monitoring cron job
Set up automated monitoring to run the security monitoring script every 5 minutes.
sudo crontab -e
Add this line to run monitoring every 5 minutes:
/5 * /usr/local/bin/nginx-security-monitor.sh >/dev/null 2>&1
Verify your setup
Test your rate limiting configuration and verify that security rules are working properly.
# Check NGINX configuration
sudo nginx -t
Verify NGINX is running
sudo systemctl status nginx
Test rate limiting with curl
for i in {1..15}; do curl -I http://example.com; sleep 1; done
Check rate limiting logs
sudo tail -f /var/log/nginx/access.log | grep "429\|403"
Monitor real-time connections
watch 'ss -tuln | grep :80'
Check security zones status
sudo nginx -T | grep -A 10 "limit_req_zone"
Performance tuning
Optimize rate limiting performance for high-traffic environments by adjusting zone sizes and caching parameters.
# Performance optimization for rate limiting
http {
# Increase worker processes for better performance
worker_processes auto;
worker_rlimit_nofile 65535;
# Optimize event handling
events {
worker_connections 4096;
use epoll;
multi_accept on;
}
# Optimize rate limiting zones for high traffic
limit_req_zone $binary_remote_addr zone=general_fast:50m rate=50r/s;
limit_req_zone $binary_remote_addr zone=api_fast:50m rate=1000r/m;
# Enable gzip compression to reduce bandwidth
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript;
# Optimize TCP settings
sendfile on;
tcp_nopush on;
tcp_nodelay on;
}
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| NGINX fails to start | Configuration syntax error | sudo nginx -t to check config syntax |
| Rate limiting not working | Zone misconfiguration | Verify zone names match in limit_req_zone and limit_req directives |
| Legitimate users getting blocked | Rate limits too strict | Increase burst values and adjust rates based on traffic analysis |
| GeoIP blocking not working | Missing GeoIP database | Install GeoIP database: sudo apt install geoip-database |
| High memory usage | Rate limiting zones too large | Reduce zone sizes or implement zone cleanup |
| Logs not rotating | Logrotate configuration error | Test logrotate: sudo logrotate -d /etc/logrotate.d/nginx-security |
| 429 errors for API clients | Burst setting too low | Increase burst parameter for API endpoints |
| Monitoring script not running | Permissions or cron issues | Check script permissions and cron logs: sudo journalctl -u cron |
Next steps
- Install and configure NGINX with HTTP/3 and modern security headers
- Install and configure Fail2ban with advanced rules and email alerts
- Configure NGINX reverse proxy with SSL termination and load balancing
- Set up NGINX Web Application Firewall with ModSecurity rules
- Configure NGINX monitoring with Prometheus and Grafana dashboards
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# NGINX Rate Limiting and DDoS Protection Setup Script
# Production-quality installation with auto-distro detection
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Usage message
usage() {
echo "Usage: $0 [domain_name]"
echo "Example: $0 example.com"
exit 1
}
# Error handling and cleanup
cleanup() {
if [[ -f /etc/nginx/nginx.conf.backup ]]; then
echo -e "${YELLOW}[CLEANUP] Restoring original nginx.conf${NC}"
mv /etc/nginx/nginx.conf.backup /etc/nginx/nginx.conf
fi
systemctl reload nginx 2>/dev/null || true
}
trap cleanup ERR
# Check if running as root
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}This script must be run as root${NC}"
exit 1
fi
# Parse arguments
DOMAIN=${1:-"_"}
if [[ $# -gt 1 ]]; then
usage
fi
echo -e "${GREEN}Starting NGINX Rate Limiting and DDoS Protection Setup${NC}"
# Auto-detect distribution
echo -e "${YELLOW}[1/8] Detecting distribution...${NC}"
if [[ ! -f /etc/os-release ]]; then
echo -e "${RED}Cannot detect distribution. /etc/os-release not found.${NC}"
exit 1
fi
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update"
PKG_INSTALL="apt install -y"
GEOIP_PACKAGE="geoip-database"
NGINX_MODULES="nginx-module-njs"
GEOIP_PATH="/usr/share/GeoIP/GeoIP.dat"
SITES_DIR="/etc/nginx/sites-available"
;;
almalinux|rocky|centos|rhel|ol)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
GEOIP_PACKAGE="GeoIP GeoIP-devel"
NGINX_MODULES="nginx-mod-http-geoip"
GEOIP_PATH="/usr/share/GeoIP/GeoIP.dat"
SITES_DIR="/etc/nginx/conf.d"
;;
fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
GEOIP_PACKAGE="GeoIP GeoIP-devel"
NGINX_MODULES="nginx-mod-http-geoip"
GEOIP_PATH="/usr/share/GeoIP/GeoIP.dat"
SITES_DIR="/etc/nginx/conf.d"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum update -y"
PKG_INSTALL="yum install -y"
GEOIP_PACKAGE="GeoIP GeoIP-devel"
NGINX_MODULES=""
GEOIP_PATH="/usr/share/GeoIP/GeoIP.dat"
SITES_DIR="/etc/nginx/conf.d"
;;
*)
echo -e "${RED}Unsupported distribution: $ID${NC}"
exit 1
;;
esac
echo -e "${GREEN}Detected: $PRETTY_NAME${NC}"
# Update system and install packages
echo -e "${YELLOW}[2/8] Updating system and installing NGINX...${NC}"
$PKG_UPDATE
$PKG_INSTALL nginx $GEOIP_PACKAGE $NGINX_MODULES
# Backup original nginx.conf
echo -e "${YELLOW}[3/8] Backing up original configuration...${NC}"
cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.backup
# Create rate limiting configuration
echo -e "${YELLOW}[4/8] Configuring rate limiting zones...${NC}"
cat > /etc/nginx/conf.d/rate-limiting.conf << 'EOF'
# Rate limiting zones configuration
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_req_zone $binary_remote_addr zone=api:10m rate=100r/m;
limit_req_zone $binary_remote_addr zone=search:10m rate=5r/s;
limit_req_zone $binary_remote_addr zone=upload:10m rate=1r/m;
# Connection limiting zones
limit_conn_zone $binary_remote_addr zone=conn_limit_per_ip:10m;
limit_conn_zone $server_name zone=conn_limit_per_server:10m;
# Real IP configuration for trusted proxies
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 172.16.0.0/12;
set_real_ip_from 192.168.0.0/16;
set_real_ip_from 203.0.113.0/24;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
EOF
# Create GeoIP and DDoS protection configuration
echo -e "${YELLOW}[5/8] Configuring GeoIP and DDoS protection...${NC}"
cat > /etc/nginx/conf.d/ddos-protection.conf << EOF
# GeoIP configuration
geoip_country $GEOIP_PATH;
# Map for blocked countries (blacklist)
map \$geoip_country_code \$blocked_country {
default 0;
CN 1;
RU 1;
}
# Block suspicious user agents
map \$http_user_agent \$blocked_agent {
default 0;
~*bot 1;
~*crawler 1;
~*spider 1;
~*scanner 1;
"" 1;
"-" 1;
}
# Block invalid request methods
map \$request_method \$not_allowed_method {
default 1;
GET 0;
POST 0;
HEAD 0;
OPTIONS 0;
}
# Request size and timeout limits
client_body_buffer_size 16k;
client_header_buffer_size 1k;
client_max_body_size 2m;
large_client_header_buffers 4 8k;
client_body_timeout 15;
client_header_timeout 15;
keepalive_timeout 30;
send_timeout 15;
server_tokens off;
EOF
# Create secure server block
echo -e "${YELLOW}[6/8] Creating secure server configuration...${NC}"
cat > "$SITES_DIR/secure-${DOMAIN}.conf" << 'EOF'
server {
listen 80;
server_name _;
# Apply rate limiting
limit_req zone=general burst=20 nodelay;
limit_conn conn_limit_per_ip 10;
limit_conn conn_limit_per_server 100;
# Block based on conditions
if ($blocked_country) {
return 444;
}
if ($blocked_agent) {
return 444;
}
if ($not_allowed_method) {
return 405;
}
# 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'" always;
# Special rate limiting for sensitive endpoints
location /login {
limit_req zone=login burst=5 nodelay;
try_files $uri $uri/ =404;
}
location /api/ {
limit_req zone=api burst=10 nodelay;
try_files $uri $uri/ =404;
}
location /search {
limit_req zone=search burst=10 nodelay;
try_files $uri $uri/ =404;
}
location /upload {
limit_req zone=upload burst=2 nodelay;
try_files $uri $uri/ =404;
}
# Default location
location / {
try_files $uri $uri/ =404;
}
# Hide nginx version and server info
server_tokens off;
}
EOF
# Update main nginx.conf to load modules
echo -e "${YELLOW}[7/8] Updating main NGINX configuration...${NC}"
sed -i '1i\# Load security modules' /etc/nginx/nginx.conf
if [[ "$ID" == "ubuntu" || "$ID" == "debian" ]]; then
sed -i '2i\load_module modules/ngx_http_geoip_module.so;' /etc/nginx/nginx.conf
sed -i '3i\load_module modules/ngx_http_realip_module.so;' /etc/nginx/nginx.conf
fi
# Enable site for Debian-based systems
if [[ "$ID" == "ubuntu" || "$ID" == "debian" ]]; then
ln -sf "$SITES_DIR/secure-${DOMAIN}.conf" "/etc/nginx/sites-enabled/secure-${DOMAIN}.conf"
rm -f /etc/nginx/sites-enabled/default
fi
# Set proper permissions
chown -R root:root /etc/nginx/conf.d/
chmod 644 /etc/nginx/conf.d/*.conf
chmod 644 "$SITES_DIR/secure-${DOMAIN}.conf"
# Configure firewall
if command -v ufw >/dev/null 2>&1; then
ufw allow 'Nginx Full' 2>/dev/null || true
elif command -v firewall-cmd >/dev/null 2>&1; then
firewall-cmd --permanent --add-service=http 2>/dev/null || true
firewall-cmd --permanent --add-service=https 2>/dev/null || true
firewall-cmd --reload 2>/dev/null || true
fi
# Test and start NGINX
echo -e "${YELLOW}[8/8] Testing configuration and starting NGINX...${NC}"
nginx -t
systemctl enable nginx
systemctl restart nginx
# Verification checks
echo -e "${YELLOW}Performing verification checks...${NC}"
if systemctl is-active --quiet nginx; then
echo -e "${GREEN}✓ NGINX is running${NC}"
else
echo -e "${RED}✗ NGINX is not running${NC}"
exit 1
fi
if nginx -t 2>/dev/null; then
echo -e "${GREEN}✓ NGINX configuration is valid${NC}"
else
echo -e "${RED}✗ NGINX configuration has errors${NC}"
exit 1
fi
if [[ -f /etc/nginx/conf.d/rate-limiting.conf ]]; then
echo -e "${GREEN}✓ Rate limiting configuration created${NC}"
fi
if [[ -f /etc/nginx/conf.d/ddos-protection.conf ]]; then
echo -e "${GREEN}✓ DDoS protection configuration created${NC}"
fi
echo -e "${GREEN}NGINX Rate Limiting and DDoS Protection setup completed successfully!${NC}"
echo -e "${YELLOW}Configuration files created:${NC}"
echo " - /etc/nginx/conf.d/rate-limiting.conf"
echo " - /etc/nginx/conf.d/ddos-protection.conf"
echo " - $SITES_DIR/secure-${DOMAIN}.conf"
echo -e "${YELLOW}Backup saved: /etc/nginx/nginx.conf.backup${NC}"
echo -e "${YELLOW}You can customize rate limits and blocking rules in the configuration files.${NC}"
Review the script before running. Execute with: bash install.sh