← News
/PLAYBOOK · 2026-07-30

HTTP Flood Attack Defense in Practice: Nginx Multi-Layer Rate Limiting Configuration

HTTP flood attacks exhaust server resources with large volumes of seemingly legitimate HTTP requests. Covers Nginx limit_req, limit_conn, connection limits, and UA filtering for multi-layer defense, plus an access.log attack-signature detection script.

Scope

HTTP flood is a common DDoS form. Attackers send large volumes of seemingly legitimate GET/POST requests from botnets or cloud hosts, exhausting CPU, memory, or bandwidth so legitimate users cannot access the service. Applies to Nginx reverse-proxy environments: CMS, APIs, e-commerce.

Prerequisites

How it works

HTTP flood differs from Slowloris. Slowloris holds connections open to exhaust the pool; HTTP flood sends complete HTTP requests at high speed. Distinguishing signals: RPS and request-signature distribution. Defense compares traffic to a normal-user baseline, then rate-limits anomalous high-frequency clients.

Procedure

Step 1: Nginx base rate-limit config

Define zones in the `nginx.conf` http block:

# /etc/nginx/nginx.conf -- inside http block

# Request rate-limit zone: by client IP
# rate=30r/s -- average max 30 requests per second
# burst=50 -- burst buffer of 50 requests
# nodelay -- process burst immediately (reject overflow)
limit_req_zone $binary_remote_addr zone=per_ip:10m rate=30r/s;

# Concurrent connection limit: max 20 connections per IP
limit_conn_zone $binary_remote_addr zone=conn_per_ip:10m;

# Server-level rate limit (global)
limit_req_zone $server_name zone=global:10m rate=500r/s;

Enable in the target server/location:

# /etc/nginx/conf.d/default.conf -- inside server block

server {
    listen 443 ssl;
    server_name your-site.com;

    limit_req zone=per_ip burst=50 nodelay;
    limit_conn conn_per_ip 20;
    limit_req zone=global burst=1000 nodelay;

    limit_req_status 429;
    limit_conn_status 429;

    error_page 429 /429.html;
    location = /429.html {
        root /var/www/html;
        internal;
    }

    location / {
        proxy_pass http://backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    # Stricter limits on login/API endpoints
    location /wp-login.php {
        limit_req zone=per_ip burst=5 nodelay;
        limit_conn conn_per_ip 5;
        proxy_pass http://backend;
    }

    location /api/ {
        limit_req zone=per_ip burst=10 nodelay;
        limit_conn conn_per_ip 10;
        proxy_pass http://backend_api;
    }
}

Step 2: UA and request-signature filtering

Attackers often reuse a fixed or obviously fake User-Agent. Nginx UA blacklist:

# UA blacklist map
map $http_user_agent $bad_ua {
    default 0;
    "~*curl" 1;
    "~*wget" 1;
    "~*python-requests" 1;
    "~*Go-http-client" 1;
    "~*Java/" 1;
    "~*libwww-perl" 1;
    "~*HTTPie" 1;
    "" 1;
}

server {
    if ($bad_ua) { return 403; }
}

Use UA filters carefully; they false-positive crawlers and API clients. Prefer combined rate + UA checks.

Step 3: Path-based rate limiting

limit_req_zone $binary_remote_addr zone=static:10m rate=100r/s;
limit_req_zone $binary_remote_addr zone=dynamic:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=search:10m rate=5r/s;

server {
    # Static assets -- looser limit
    location ~* \.(jpg|jpeg|png|gif|css|js|ico|woff2?)$ {
        limit_req zone=static burst=200 nodelay;
        expires 30d;
        add_header Cache-Control "public";
    }

    # Dynamic pages -- moderate limit
    location / {
        limit_req zone=dynamic burst=20 nodelay;
    }

    # Search/query -- strict limit
    location ~* /(search|query|s\.php) {
        limit_req zone=search burst=5 nodelay;
    }
}

Step 4: Log analysis script

Python script against Nginx access.log: high-frequency IPs and attack signatures.

#!/usr/bin/env python3
"""
Nginx HTTP Flood attack signature analysis script
Usage: python3 flood_detect.py /var/log/nginx/access.log
"""
import sys, re, collections

LOG_PATTERN = re.compile(
    r'(?P[\d.]+) .* \[(?P[^\]]+)\] '
    r'"(?P\w+) (?P\S+) .*" '
    r'(?P\d+) (?P\d+)'
)

def parse_log(filepath, window_secs=60, threshold=100):
    ip_counter = collections.Counter()
    ip_details = {}

    with open(filepath, 'r') as f:
        for line in f:
            m = LOG_PATTERN.search(line)
            if not m:
                continue
            ip = m.group('ip')
            ip_counter[ip] += 1
            if ip not in ip_details:
                ip_details[ip] = {
                    'paths': collections.Counter(),
                    'methods': collections.Counter(),
                    'statuses': collections.Counter(),
                }
            ip_details[ip]['paths'][m.group('path')] += 1
            ip_details[ip]['methods'][m.group('method')] += 1
            ip_details[ip]['statuses'][m.group('status')] += 1

    print("{: threshold * 5:
            flags.append("high-freq attack")
        if details['statuses'].get('404', 0) > count * 0.3:
            flags.append("scanner")
        if details['statuses'].get('403', 0) > count * 0.5:
            flags.append("blocked")
        if details['methods']['POST'] > count * 0.5:
            flags.append("mass POST")

        flag_str = " [" + ", ".join(flags) + "]" if flags else ""
        print("{: [window_secs] [threshold]")
        sys.exit(1)
    window = int(sys.argv[2]) if len(sys.argv) > 2 else 60
    threshold = int(sys.argv[3]) if len(sys.argv) > 3 else 100
    parse_log(sys.argv[1], window, threshold)

Step 5: Auto-ban script

#!/bin/bash
# auto_ban.sh -- auto-ban high-frequency IPs
LOG="/var/log/nginx/access.log"
BAN_LIST="/etc/nginx/conf.d/ban_ip.conf"
THRESHOLD=500
WINDOW=60

python3 flood_detect.py "$LOG" "$WINDOW" "$THRESHOLD" | grep "high-freq attack" | \
    awk '{print $1}' | while read ip; do
    if ! grep -q "$ip" "$BAN_LIST" 2>/dev/null; then
        echo "deny $ip;" >> "$BAN_LIST"
        echo "[BAN] $ip"
    fi
done

nginx -s reload

Add to cron, every 5 minutes:

*/5 * * * * /usr/local/bin/auto_ban.sh >> /var/log/auto_ban.log 2>&1

Verification

# 1. Check Nginx config syntax
nginx -t

# 2. Simulate high-frequency requests with ab
ab -n 1000 -c 50 https://your-site.com/

# 3. Test per-IP rate limit
for i in $(seq 1 100); do
    curl -s -o /dev/null -w "%{http_code}\n" https://your-site.com/ &
done
# expect some 429s

# 4. Check 429 records in access.log
grep "429" /var/log/nginx/access.log | head -10

FAQ

Q1: How do limit_req burst and nodelay interact?

`burst=N` queues requests that instantaneously exceed rate; above N they are rejected. Without `nodelay`, queued requests drain at `rate` (adds latency). With `nodelay`, queued requests are processed immediately; overflow beyond burst is rejected. Use `nodelay` on APIs. Omit `nodelay` on normal page traffic for smoother limiting.

Q2: After rate limiting, how do you separate a legitimate burst from an attack?

Three signals: path distribution (attackers concentrate on a few paths), User-Agent diversity (attack UAs are often single or obviously fake), source IP distribution (CDN origin IPs are stable; attack IPs are scattered). Return 429 (not 403) to suspects, then a frontend JS challenge (reCAPTCHA) to confirm.

Summary

HTTP flood defense stacks Nginx limiters: `limit_req` (request rate), `limit_conn` (concurrency), UA filtering, and log-based adaptive bans. No universal config; tune against real traffic. Combine with CDN WAF and cloud DDoS (Cloudflare, Alibaba Cloud Anti-DDoS).