Set up NGINX rate limiting and security headers for DDoS protection

Intermediate 25 min May 24, 2026 1,079 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



    
    


    <h1>Too Many Requests</h1>
    <p>Please slow down and try again later.</p>

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: 
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

Prefere não gerir isto sozinho?

Gerimos a infraestrutura de empresas que dependem do tempo de atividade. Totalmente gerida, com um contacto fixo que conhece o seu ambiente.

Tem um contacto fixo que conhece o seu ambiente

Roterdão 03:56 · acessível por mensagem, sem formulário de tickets