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 aRetry-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).