Configure NGINX rate limiting and advanced security rules for DDoS protection

Intermediate 25 min Apr 16, 2026 907 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Configure NGINX with comprehensive rate limiting, connection throttling, and advanced security headers to protect your web applications from DDoS attacks and malicious traffic. Learn to implement zone-based rate limiting, geographic blocking, and real-time monitoring for production environments.

Prerequisites

  • Root or sudo access
  • Basic understanding of NGINX configuration
  • Familiarity with iptables firewall rules

What this solves

DDoS attacks and malicious traffic can overwhelm your web servers, causing service outages and performance degradation. This tutorial shows you how to configure NGINX with advanced rate limiting, connection controls, and security headers to protect your applications from various attack vectors including volumetric attacks, application-layer attacks, and bot traffic.

Step-by-step configuration

Update system packages

Start by updating your package manager to ensure you get the latest NGINX version with security patches.

sudo apt update && sudo apt upgrade -y
sudo dnf update -y

Install NGINX with additional modules

Install NGINX along with the GeoIP module for geographic blocking capabilities.

sudo apt install -y nginx nginx-module-geoip
sudo nginx -V
sudo dnf install -y nginx nginx-mod-http-geoip
sudo nginx -V

Configure main NGINX security settings

Create the main NGINX configuration with security-focused settings and rate limiting zones.

user www-data;
worker_processes auto;
worker_rlimit_nofile 65535;
pid /run/nginx.pid;

include /etc/nginx/modules-enabled/*.conf;

events {
    worker_connections 4096;
    use epoll;
    multi_accept on;
}

http {
    # Basic settings
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 30;
    keepalive_requests 1000;
    types_hash_max_size 2048;
    server_tokens off;
    
    # Rate limiting zones
    limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
    limit_req_zone $binary_remote_addr zone=api:10m rate=20r/s;
    limit_req_zone $binary_remote_addr zone=global:10m rate=10r/s;
    limit_req_zone $server_name zone=perserver:10m rate=100r/s;
    
    # Connection limiting
    limit_conn_zone $binary_remote_addr zone=perip:10m;
    limit_conn_zone $server_name zone=perserver_conn:10m;
    
    # Request size limits
    client_max_body_size 10M;
    client_body_buffer_size 128k;
    client_header_buffer_size 3m;
    large_client_header_buffers 4 256k;
    
    # Timeout settings
    client_body_timeout 10s;
    client_header_timeout 10s;
    send_timeout 10s;
    
    # Security headers map
    map $sent_http_content_type $expires {
        default                    off;
        text/html                  epoch;
        text/css                   max;
        application/javascript     max;
        ~image/                   1M;
    }
    
    expires $expires;
    
    # Logging
    log_format security '$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"';
    
    access_log /var/log/nginx/access.log security;
    error_log /var/log/nginx/error.log warn;
    
    # MIME types
    include /etc/nginx/mime.types;
    default_type application/octet-stream;
    
    # 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_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    
    # Include server configurations
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

Create DDoS protection configuration

Create a dedicated configuration file for advanced DDoS protection rules.

# Block common attack patterns
map $http_user_agent $blocked_agent {
    default 0;
    ~*malicious 1;
    ~*attack 1;
    ~*hack 1;
    "" 1;
}

# Block suspicious request methods
map $request_method $blocked_method {
    default 0;
    ~*^(TRACE|DELETE|PATCH)$ 1;
}

# Rate limiting for different endpoints
map $request_uri $request_uri_path {
    default "global";
    ~^/login "login";
    ~^/api/ "api";
    ~^/admin "login";
    ~^/wp-admin "login";
    ~^/wp-login "login";
}

# Geographic blocking (requires GeoIP database)
# Uncomment and configure based on your needs
# map $geoip_country_code $blocked_country {
#     default 0;
#     ~^(CN|RU|KP)$ 1;
# }

# Custom error pages for rate limiting
error_page 429 /429.html;
error_page 444 /444.html;

Configure virtual host with security rules

Create a virtual host configuration that implements the DDoS protection measures.

server {
    listen 80;
    listen 443 ssl http2;
    server_name example.com www.example.com;
    
    # SSL configuration (adjust paths as needed)
    ssl_certificate /etc/ssl/certs/example.com.crt;
    ssl_certificate_key /etc/ssl/private/example.com.key;
    ssl_dhparam /etc/ssl/certs/dhparam.pem;
    
    # Root directory
    root /var/www/html;
    index index.html index.php;
    
    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'" always;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    
    # Connection and request limits
    limit_conn perip 10;
    limit_conn perserver_conn 1000;
    
    # Block malicious agents and methods
    if ($blocked_agent) {
        return 444;
    }
    
    if ($blocked_method) {
        return 405;
    }
    
    # Block empty user agents
    if ($http_user_agent = "") {
        return 444;
    }
    
    # Block requests without Host header
    if ($host !~* ^(example\.com|www\.example\.com)$) {
        return 444;
    }
    
    # Rate limiting for login endpoints
    location ~* ^/(login|admin|wp-admin|wp-login) {
        limit_req zone=login burst=3 nodelay;
        limit_req_status 429;
        
        # Additional security for admin areas
        # Uncomment to restrict by IP
        # allow 203.0.113.10;
        # deny all;
        
        try_files $uri $uri/ =404;
    }
    
    # Rate limiting for API endpoints
    location ^~ /api/ {
        limit_req zone=api burst=50 nodelay;
        limit_req_status 429;
        
        # API-specific headers
        add_header X-RateLimit-Limit "20/s" always;
        add_header X-RateLimit-Remaining $limit_req_status always;
        
        try_files $uri $uri/ =404;
    }
    
    # Main location with global rate limiting
    location / {
        limit_req zone=global burst=20 nodelay;
        limit_req_status 429;
        
        # Block common exploit attempts
        location ~* \.(php|asp|aspx|jsp)$ {
            return 444;
        }
        
        # Block access to sensitive files
        location ~* \.(sql|bak|backup|log|old|tmp|temp)$ {
            return 444;
        }
        
        # Block dot files
        location ~ /\. {
            return 444;
        }
        
        try_files $uri $uri/ =404;
    }
    
    # Custom error pages
    location = /429.html {
        root /var/www/errors;
        internal;
    }
    
    location = /444.html {
        root /var/www/errors;
        internal;
    }
    
    # Security-focused error handling
    error_page 400 401 402 403 404 /40x.html;
    error_page 500 502 503 504 /50x.html;
    
    location = /40x.html {
        root /var/www/errors;
        internal;
    }
    
    location = /50x.html {
        root /var/www/errors;
        internal;
    }
}

Create custom error pages

Create custom error pages to avoid revealing server information during attacks.

sudo mkdir -p /var/www/errors
sudo chown www-data:www-data /var/www/errors



    
    


    <h1>Too Many Requests</h1>
    <p>You have been rate limited. Please try again later.</p>




    
    


    <h1>Access Denied</h1>
    <p>The requested resource is not available.</p>

Configure log rotation for security monitoring

Set up log rotation to manage the increased logging from security events.

/var/log/nginx/*.log {
    daily
    missingok
    rotate 30
    compress
    delaycompress
    notifempty
    create 644 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
}

Create monitoring script for attack detection

Create a script to monitor for potential attacks and suspicious activity.

#!/bin/bash

# NGINX Attack Monitoring Script
LOG_FILE="/var/log/nginx/access.log"
ALERT_THRESHOLD=100
TIME_WINDOW=60  # seconds
BAN_TIME=3600   # 1 hour
TEMP_FILE="/tmp/nginx-attacks.tmp"

# Function to ban IP using iptables
ban_ip() {
    local ip=$1
    echo "$(date): Banning IP $ip for $BAN_TIME seconds" >> /var/log/nginx-monitor.log
    iptables -I INPUT -s $ip -j DROP
    # Schedule unban
    echo "sleep $BAN_TIME && iptables -D INPUT -s $ip -j DROP" | at now
}

# Monitor for rate limit violations (429 status)
if [ -f "$LOG_FILE" ]; then
    # Get IPs with excessive 429 responses in the last minute
    tail -n 10000 $LOG_FILE | \
    awk -v threshold="$ALERT_THRESHOLD" -v window="$TIME_WINDOW" '
    BEGIN {
        now = systime()
    }
    {
        # Parse timestamp and convert to epoch
        gsub(/\[|\]/, "", $4)
        gsub(/:/, " ", $4)
        gsub(/\//, " ", $4)
        cmd = "date -d \"" $4 "\" +%s"
        cmd | getline timestamp
        close(cmd)
        
        # Check if within time window and status is 429
        if (now - timestamp <= window && $9 == "429") {
            count[$1]++
        }
    }
    END {
        for (ip in count) {
            if (count[ip] >= threshold) {
                print ip, count[ip]
            }
        }
    }' > $TEMP_FILE
    
    # Ban IPs that exceed threshold
    while read ip count; do
        if [ ! -z "$ip" ]; then
            # Check if IP is not already banned
            if ! iptables -L INPUT | grep -q $ip; then
                ban_ip $ip
            fi
        fi
    done < $TEMP_FILE
    
    rm -f $TEMP_FILE
fi
sudo chmod +x /usr/local/bin/nginx-monitor.sh
sudo chown root:root /usr/local/bin/nginx-monitor.sh

Set up automated monitoring

Configure a cron job to run the monitoring script regularly.

sudo crontab -e
# Monitor for NGINX attacks every minute
* * * * * /usr/local/bin/nginx-monitor.sh

# Clean up old monitor logs weekly
0 2 * * 0 find /var/log -name "nginx-monitor.log" -mtime +7 -delete

Enable and start services

Enable NGINX and test the configuration before starting.

sudo nginx -t
sudo ln -sf /etc/nginx/sites-available/secure-site /etc/nginx/sites-enabled/
sudo systemctl enable nginx
sudo systemctl restart nginx
sudo systemctl status nginx

Configure advanced security monitoring

Install and configure Fail2Ban integration

Integrate Fail2Ban with NGINX logs for automated IP banning based on attack patterns.

sudo apt install -y fail2ban
sudo dnf install -y fail2ban
[nginx-rate-limit]
enabled = true
filter = nginx-rate-limit
logpath = /var/log/nginx/access.log
maxretry = 10
findtime = 60
bantime = 3600
action = iptables[name=nginx-rate-limit, port=http, protocol=tcp]

[nginx-bad-request]
enabled = true
filter = nginx-bad-request
logpath = /var/log/nginx/access.log
maxretry = 5
findtime = 60
bantime = 3600
action = iptables[name=nginx-bad-request, port=http, protocol=tcp]
[Definition]
failregex = ^
[Definition]
failregex = ^

Start Fail2Ban service

Enable and start the Fail2Ban service to begin monitoring.

sudo systemctl enable fail2ban
sudo systemctl restart fail2ban
sudo systemctl status fail2ban

Verify your setup

# Check NGINX configuration
sudo nginx -t

# Verify rate limiting zones are loaded
sudo nginx -T | grep -A 5 limit_req_zone

# Check active connections and limits
curl -I http://example.com
curl -w "%{http_code}\n" -s -o /dev/null http://example.com

# Test rate limiting (should return 429 after limits)
for i in {1..25}; do curl -s -o /dev/null -w "%{http_code} " http://example.com; done
echo

# Check Fail2Ban status
sudo fail2ban-client status
sudo fail2ban-client status nginx-rate-limit

# Monitor real-time logs
sudo tail -f /var/log/nginx/access.log | grep -E '(429|444|403)'

# Check current iptables rules
sudo iptables -L INPUT -n
Note: The rate limiting test may temporarily block your IP. Wait a few minutes or restart NGINX to reset if needed.

Performance tuning and optimization

For high-traffic sites, you may need to adjust the rate limiting parameters and connection limits based on your specific requirements. Monitor your server resources and adjust the worker processes and connection limits accordingly.

Consider implementing NGINX reverse proxy with SSL termination for better performance in clustered environments. For comprehensive monitoring, integrate with Prometheus and Grafana monitoring to track attack patterns and server performance metrics.

Common issues

SymptomCauseFix
Configuration test failsSyntax error in NGINX configsudo nginx -t to identify specific errors
Rate limiting not workingZone not defined or incorrect syntaxCheck limit_req_zone definitions and limit_req usage
All requests blockedRate limits too restrictiveIncrease burst values or rate limits in configuration
429 errors for legitimate usersRate limits too aggressiveAdjust rates and implement whitelist for trusted IPs
Fail2Ban not banning IPsLog format doesn't match filter regexTest filters with fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-rate-limit.conf
High memory usageRate limiting zones too largeReduce zone sizes or implement zone cleanup
SSL/TLS errorsCertificate configuration issuesCheck certificate paths and permissions with nginx -t
Geographic blocking not workingGeoIP database not installedInstall GeoIP database and configure module properly

Next steps

Automated install script

Run this to automate the entire setup

Wil je dit niet zelf beheren?

Wij beheren infrastructuur voor bedrijven die afhankelijk zijn van uptime. Volledig beheerd, met één vast aanspreekpunt dat je omgeving kent.

U krijgt één vast aanspreekpunt dat uw omgeving kent

Op kantoor in Rotterdam 12:15 · bereikbaar in een bericht, geen ticketformulier