Live Blog

Stop Abusive Requests on GridPane: Nginx Rate Limiting and Fail2ban Escalating Bans

This page has had its content updated on September 12, 2025 EDT by Jordan

Content Error or Suggest an Edit

Notice a grammatical error or technical inaccuracy? Let us know; we will give you credit!

Introduction

One of the most common problems on WordPress servers is requests hammering your PHP backend when caching isn’t available. While static assets (CSS, JS, images) are usually served quickly from cache or disk, crawlers often bypass cache and fire 10+ PHP requests per second. This can end up overwhelming PHP workers and slow down your site or entire server. The solution? Nginx rate limiting with Fail2ban escalating bans.

Step 1. Define a Rate Limit Zone in Nginx

First, we create a shared memory zone in Nginx that tracks how many requests each IP makes per second. Create /etc/nginx/conf.d/php-limit-zone.conf:

# Allow 1 PHP request per second per IP (≈60/minute)
limit_req_zone $binary_remote_addr zone=php_limit:10m rate=1r/s;
  • php_limit → the name of the limiter zone.
  • 10m → 10MB of memory, enough for ~160k IPs.
  • rate=1r/s → allows ~60 PHP requests/minute per IP.

Step 2. Enforce Limits on PHP Requests

GridPane uses per-site include files. For PHP limits, create: /var/www/example.com/nginx/php-rate-limit-php-context.conf

# Enforce limiter inside PHP location
limit_req zone=php_limit burst=2 nodelay;

# Helpful headers for debugging
add_header X-RateLimit-Status $limit_req_status always;
add_header Retry-After 30 always;

This means:

  • Each IP can average 1 request/sec.
  • Short bursts of 2 extra requests are tolerated.
  • After that, extra requests are rejected immediately.
  • Clients receive a 429 Too Many Requests with a Retry-After: 30 header.

Step 3. Install Fail2ban

Attention

If you’re on GridPane, you already have Fail2ban, so you can skip this step. However, if you’re on another platform, you can utilize the following.

Fail2ban monitors logs and applies firewall bans. On Ubuntu:

sudo apt update
sudo apt install fail2ban

Step 4. Create a Fail2ban Filter

Attention

If you’re on GridPane, you already have Fail2ban, so you can skip this step. However, if you’re on another platform, you can utilize the following.

Tell Fail2ban how to recognize Nginx rate-limit events in logs: /etc/fail2ban/filter.d/nginx-limit-req.conf

[Definition]
failregex = ^\s*\[[a-z]+\] \d+#\d+: \*\d+ limiting requests, excess: [\d\.]+ by zone "(?:%(ngx_limit_req_zones)s)", client: <HOST>,
ignoreregex =

This matches lines like:

limiting requests, excess: 5.000 by zone "php_limit" of client: 192.0.2.1, server: example.com

Step 5. Add Jails with Escalating Bans

There are two options, depending on which version of Fail2ban you have.

Pre Fail2ban 0.11 (Legacy Method)

Here’s the GridPane-specific jail configuration with escalating bans:

First offense = 30 seconds

Create etc/fail2ban/jail.d/nginx-limit-req-short.conf

[nginx-limit-req-short]
enabled  = true
port     = http,https
filter   = nginx-limit-req
logpath  = /var/log/nginx/example.com.error.log
maxretry = 1
findtime = 60
bantime  = 30

Second offense (within 1 hour) = 24 hours

Create /etc/fail2ban/jail.d/nginx-limit-req-medium.conf

[nginx-limit-req-medium]
enabled  = true
port     = http,https
filter   = nginx-limit-req
logpath  = /var/log/nginx/example.com.error.log
maxretry = 2
findtime = 3600
bantime  = 86400

Third offense (within 24 hours) = 48 hours

Create /etc/fail2ban/jail.d/nginx-limit-req-long.conf

[nginx-limit-req-long]
enabled  = true
port     = http,https
filter   = nginx-limit-req
logpath  = /var/log/nginx/example.com.error.log
maxretry = 3
findtime = 86400
bantime  = 172800

Fail2ban 0.11 and Greater (Simplified with bantime.increment)

Fail2ban 0.11+ supports ban-time incrementing directly, so you don’t need multiple jails.

/etc/fail2ban/jail.d/nginx-limit-req.conf

[nginx-limit-req]
enabled   = true
port      = http,https
filter    = nginx-limit-req
logpath   = /var/log/nginx/example.com.error.log
maxretry  = 1
findtime  = 60

# Initial bantime: 30s
bantime   = 30

# Escalating bans
bantime.increment      = true
bantime.factor         = 2
bantime.formula        = bantime * (1 << (failures - 1))
bantime.maxtime        = 172800   # Cap at 48h

This means:

  • First offense = 30s
  • Second offense = 60s
  • Third offense = 120s
  • Escalates exponentially up to 48h max

You can tweak the formula and factor for more aggressive escalation (e.g., jump straight to 24h bans).

Step 6. Restart Fail2ban

sudo systemctl restart fail2ban

Check status:

sudo fail2ban-client status nginx-limit-req-short
sudo fail2ban-client status nginx-limit-req-medium
sudo fail2ban-client status nginx-limit-req-long

Step 7. Test

Use ApacheBench or curl to simulate abusive traffic:

ab -n 50 -c 10 https://example.com/index.php
  • Normal users → fine.
  • Crawlers hitting 10 requests/sec → banned.
  • Repeat abusers → escalate from 30s → 24h → 48h bans.

Why This Works

  • Nginx limit_req: protects PHP instantly by throttling.
  • Fail2ban: enforces real bans at firewall level.
  • Escalation: prevents repeat offenders from constantly hammering your server.

Recap

With this setup:

  • Humans can click around normally.
  • Bad crawlers get slapped with 429s, then banned.
  • Repeat offenders are escalated from a slap (30s) to full lockouts (days).
0 Shares: