Set up NGINX rate limiting and security headers for DDoS protection

Intermediate 25 min May 24, 2026 29 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

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
sudo dnf update -y
sudo dnf install -y nginx
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
sudo dnf 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

SettingPurposeRecommended Value
worker_processesCPU utilizationauto (matches CPU cores)
worker_connectionsConcurrent connections1024-4096
keepalive_timeoutConnection reuse30-65 seconds
zone memory sizeRate limit storage10m per million IPs
burst sizeTraffic spike handling10-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

SymptomCauseFix
429 errors for legitimate usersRate limits too strictIncrease burst size or rate in zones
High memory usageZone size too largeReduce zone memory allocation
fail2ban not workingLog path incorrectCheck logpath in jail configuration
Security headers not appearingInclude not loadedVerify include directive in server block
Rate limiting not workingMissing zone applicationAdd limit_req directive to location blocks
Config test failsSyntax errorRun sudo nginx -t for detailed error
Warning: Be careful with aggressive rate limiting on production sites. Always test with your normal traffic patterns first, and monitor legitimate user impact. Consider implementing a whitelist for trusted IPs or authenticated users.

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;
    }
}
For SSL certificate management, you can reference our guide on configuring NGINX SSL termination with Certbot.

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

Running this in production?

Want this handled for you? Setting up rate limiting once is straightforward. Keeping it tuned, monitored, and effective against evolving attack patterns 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.