Configure NGINX rate limiting and DDoS protection with advanced security rules

Intermediate 25 min Apr 26, 2026 86 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Set up NGINX rate limiting modules, implement connection limits, and configure geographic blocking to protect your web applications from DDoS attacks and abuse.

Prerequisites

  • Root or sudo access
  • NGINX installed
  • Basic understanding of web server configuration

What this solves

Rate limiting and DDoS protection prevent malicious traffic from overwhelming your web servers and degrading service for legitimate users. NGINX's built-in modules let you control request rates, limit concurrent connections, and implement geographic blocking without requiring external services.

Step-by-step configuration

Update system packages and install NGINX

Start by ensuring you have the latest NGINX version with all required modules.

sudo apt update && sudo apt upgrade -y
sudo apt install -y nginx nginx-module-geoip2
sudo dnf update -y
sudo dnf install -y nginx nginx-mod-http-geoip2

Verify NGINX modules are loaded

Check that the rate limiting and GeoIP modules are available for configuration.

nginx -V 2>&1 | grep -o with-http_limit_req_module
nginx -V 2>&1 | grep -o with-http_limit_conn_module
nginx -V 2>&1 | grep -o with-http_geoip2_module

Configure basic rate limiting zones

Define rate limiting zones in the main NGINX configuration. These zones track client IPs and enforce request limits.

http {
    # 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=10r/s;
    limit_req_zone $binary_remote_addr zone=general:10m rate=1r/s;
    
    # Connection limiting zones
    limit_conn_zone $binary_remote_addr zone=perip:10m;
    limit_conn_zone $server_name zone=perserver:10m;
    
    # Define rate limiting log format
    log_format rate_limit '$remote_addr - $remote_user [$time_local] '
                         '"$request" $status $bytes_sent '
                         '"$http_referer" "$http_user_agent" '
                         'rt=$request_time uct="$upstream_connect_time" '
                         'uht="$upstream_header_time" urt="$upstream_response_time"';
    
    # Other existing configuration...
}

Download and configure GeoIP database

Set up geographic blocking by downloading a GeoIP database and configuring NGINX to use it.

sudo mkdir -p /etc/nginx/geoip
cd /etc/nginx/geoip
sudo wget https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-Country.mmdb
sudo chown www-data:www-data GeoLite2-Country.mmdb
sudo chmod 644 GeoLite2-Country.mmdb

Add GeoIP configuration to NGINX

Configure NGINX to load the GeoIP database and create country-based variables for blocking.

http {
    # Load GeoIP module and database
    geoip2 /etc/nginx/geoip/GeoLite2-Country.mmdb {
        auto_reload 5m;
        $geoip2_metadata_country_build metadata build_epoch;
        $geoip2_data_country_code country iso_code;
        $geoip2_data_country_name country names en;
    }
    
    # Map blocked countries
    map $geoip2_data_country_code $blocked_country {
        default 0;
        CN 1;  # China
        RU 1;  # Russia
        KP 1;  # North Korea
        # Add more country codes as needed
    }
    
    # Rate limiting zones (from previous step)
    limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
    limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
    limit_req_zone $binary_remote_addr zone=general:10m rate=1r/s;
    
    # Connection limiting zones
    limit_conn_zone $binary_remote_addr zone=perip:10m;
    limit_conn_zone $server_name zone=perserver:10m;
    
    # Rate limiting log format
    log_format rate_limit '$remote_addr - $remote_user [$time_local] '
                         '"$request" $status $bytes_sent '
                         '"$http_referer" "$http_user_agent" '
                         'rt=$request_time uct="$upstream_connect_time" '
                         'uht="$upstream_header_time" urt="$upstream_response_time" '
                         'country=$geoip2_data_country_code';
}

Create advanced security configuration

Set up a dedicated configuration file for security rules that can be included in virtual hosts.

# DDoS Protection Configuration

Block requests with suspicious user agents

map $http_user_agent $blocked_user_agent { default 0; ~*(nmap|nikto|wikto|sf|sqlmap|bsqlbf|w3af|acunetix|havij|appscan) 1; ~*(hydra|libwww-perl|BBBike|sqlninja) 1; "" 1; # Block empty user agents }

Block common attack patterns in URI

map $request_uri $blocked_uri { default 0; ~*\.\./\.\./ 1; # Directory traversal ~union.select 1; # SQL injection ~concat.\( 1; # SQL injection ~*base64_decode 1; # Code injection ~GLOBALS.\[ 1; # Global variable access ~_REQUEST.\[ 1; # Request variable access ~*proc/self/environ 1; # Environment access ~*auto_prepend_file= 1; # File inclusion ~*auto_append_file= 1; # File inclusion }

Rate limiting by request method

map $request_method $rate_limit_key { GET $binary_remote_addr; POST $binary_remote_addr; default ''; }

Limit request size to prevent large payload attacks

client_max_body_size 10m; client_body_buffer_size 128k; client_header_buffer_size 3m; large_client_header_buffers 4 256k;

Timeout settings to prevent slowloris attacks

client_body_timeout 10s; client_header_timeout 10s; keepalive_timeout 5s 5s; send_timeout 10s;

Configure a protected virtual host

Create a virtual host configuration that implements all security measures and rate limiting.

server {
    listen 80;
    server_name example.com;
    
    # Include security rules
    include /etc/nginx/conf.d/security-rules.conf;
    
    # Enable rate limiting logs
    access_log /var/log/nginx/rate_limit.log rate_limit;
    error_log /var/log/nginx/error.log;
    
    # Block based on country
    if ($blocked_country) {
        return 403 "Access denied from your location";
    }
    
    # Block suspicious user agents
    if ($blocked_user_agent) {
        return 403 "Blocked user agent";
    }
    
    # Block suspicious URI patterns
    if ($blocked_uri) {
        return 403 "Blocked request";
    }
    
    # Apply connection limits
    limit_conn perip 10;        # Max 10 connections per IP
    limit_conn perserver 100;   # Max 100 connections to server
    
    # Apply general rate limiting
    limit_req zone=general burst=5 nodelay;
    
    # Root directory
    root /var/www/html;
    index index.html index.htm;
    
    # API endpoints with stricter rate limiting
    location /api/ {
        limit_req zone=api burst=20 nodelay;
        limit_req_status 429;
        
        # Add CORS headers for API
        add_header 'Access-Control-Allow-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';
        
        try_files $uri $uri/ =404;
    }
    
    # Login endpoints with very strict rate limiting
    location /login {
        limit_req zone=login burst=3 nodelay;
        limit_req_status 429;
        
        # Additional security headers
        add_header X-Frame-Options SAMEORIGIN;
        add_header X-Content-Type-Options nosniff;
        add_header X-XSS-Protection "1; mode=block";
        
        try_files $uri $uri/ =404;
    }
    
    # Static files with relaxed rate limiting
    location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        limit_req zone=general burst=50 nodelay;
    }
    
    # Block access to sensitive files
    location ~* \.(htaccess|htpasswd|ini|log|sh|sql|tar|gz)$ {
        deny all;
    }
    
    # Custom error pages for rate limiting
    error_page 429 /429.html;
    location = /429.html {
        root /var/www/html/error-pages;
        internal;
    }
    
    error_page 403 /403.html;
    location = /403.html {
        root /var/www/html/error-pages;
        internal;
    }
}

Create custom error pages

Set up user-friendly error pages for rate limiting and blocking responses.

sudo mkdir -p /var/www/html/error-pages
sudo chown www-data:www-data /var/www/html/error-pages



    Rate Limit Exceeded
    


    

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 Forbidden
    


    

403 - Forbidden

Access to this resource is denied.

If you believe this is an error, please contact support.

Set up log rotation for rate limit logs

Configure logrotate to manage the rate limiting log files and prevent disk space issues.

/var/log/nginx/rate_limit.log {
    daily
    rotate 14
    compress
    delaycompress
    missingok
    notifempty
    create 640 www-data adm
    postrotate
        if [ -f /var/run/nginx.pid ]; then
            kill -USR1 $(cat /var/run/nginx.pid)
        fi
    endscript
}

Enable the site and restart NGINX

Activate your protected site configuration and restart NGINX to apply all changes.

sudo nginx -t
sudo ln -s /etc/nginx/sites-available/protected-site /etc/nginx/sites-enabled/
sudo systemctl reload nginx
sudo systemctl status nginx

Configure monitoring alerts

Set up basic monitoring to track rate limiting effectiveness and blocked requests.

server {
    listen 8080;
    server_name localhost;
    
    location /nginx_status {
        stub_status on;
        access_log off;
        allow 127.0.0.1;
        deny all;
    }
    
    location /rate_limit_status {
        access_log off;
        allow 127.0.0.1;
        deny all;
        
        default_type text/plain;
        return 200 "Rate limiting is active\nCheck /var/log/nginx/rate_limit.log for details\n";
    }
}
sudo systemctl reload nginx

Test your rate limiting setup

Verify that rate limiting and security rules are working correctly with these test commands.

# Test general rate limiting (should get 429 after burst limit)
for i in {1..10}; do curl -I http://example.com/; sleep 0.1; done

Test API rate limiting

for i in {1..25}; do curl -I http://example.com/api/test; sleep 0.1; done

Test login rate limiting

for i in {1..5}; do curl -I http://example.com/login; sleep 1; done

Check if suspicious user agent is blocked

curl -H "User-Agent: nmap" http://example.com/

Verify NGINX status

curl http://localhost:8080/nginx_status curl http://localhost:8080/rate_limit_status

Monitor rate limiting effectiveness

Use these commands to monitor your rate limiting and identify attack patterns.

# Monitor rate limit log in real-time
sudo tail -f /var/log/nginx/rate_limit.log

Count rate limited requests in the last hour

sudo grep "$(date -d '1 hour ago' '+%d/%b/%Y:%H')" /var/log/nginx/rate_limit.log | grep -c "429"

Find top blocked IPs

sudo awk '$9 == "429" {print $1}' /var/log/nginx/rate_limit.log | sort | uniq -c | sort -nr | head -10

Monitor connection counts

sudo ss -tuln | grep :80

Check blocked countries

sudo grep "403" /var/log/nginx/access.log | tail -20

Verify your setup

sudo nginx -t
sudo systemctl status nginx
curl -I http://example.com/
sudo tail -n 20 /var/log/nginx/rate_limit.log
sudo ls -la /etc/nginx/geoip/

Common issues

SymptomCauseFix
Rate limiting not workingZone not defined in http blockMove limit_req_zone to /etc/nginx/nginx.conf http section
GeoIP database not loadingWrong file permissionssudo chown www-data:www-data /etc/nginx/geoip/*
429 errors for all requestsRate limit too restrictiveIncrease burst value or rate in zone definition
Legitimate traffic blockedCountry blocking too broadRemove country codes from blocked list or whitelist specific IPs
Log files growing too largeNo log rotation configuredEnsure /etc/logrotate.d/nginx-rate-limit is properly configured
NGINX won't startConfiguration syntax errorRun sudo nginx -t to check configuration

Next steps

Running this in production?

Need enterprise-grade protection? Setting this up once is straightforward. Keeping it tuned, monitored, and responding to new attack patterns across environments is the harder part. See how we run infrastructure like this for European SaaS and e-commerce teams.

Automated install script

Run this to automate the entire setup

Need help?

Don't want to manage this yourself?

We handle infrastructure security hardening for businesses that depend on uptime. From initial setup to ongoing operations.