Configure NGINX rate limiting and advanced security rules for DDoS protection

Intermediate 25 min Apr 16, 2026 315 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



    Rate Limited
    


    

Too Many Requests

You have been rate limited. Please try again later.




    Access Denied
    


    

Access Denied

The requested resource is not available.

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 = ^ . "." 429 .*$
ignoreregex =
[Definition]
failregex = ^ . "." (400|401|403|444) .*$
ignoreregex =

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

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.