Rate Limiting with Nginx's limit_req

Nginx's limit_req module throttles incoming requests at the server level — before they ever touch PHP, a database, or your application. It's your first line of defense against HTTP floods, brute-force login attempts, and runaway bots that might otherwise exhaust your PHP-FPM pool and serve 502 errors to everyone else.


How it works

You define a shared memory zone with a rate limit, then apply it to specific locations. Nginx tracks requests per key (usually the visitor's IP) and drops or delays anything that exceeds the limit:

# In the http { } block — define a zone
limit_req_zone $binary_remote_addr zone=flood:10m rate=5r/s;
$binary_remote_addr
Track by IP in binary format — much smaller in memory than $remote_addr
zone=flood:10m
Create a shared memory zone named "flood" with 10 MB of space
rate=5r/s
Allow 5 requests per second per tracked key

Now apply it to a location:

location = /about.html {
    limit_req zone=flood nodelay;
}

nodelay tells Nginx to immediately reject excess requests with a 503 rather than queuing them. Without it, Nginx holds excess requests in a queue and processes them as the rate allows — which is sometimes desirable for API endpoints but usually not for page protection.


Applying it to all PHP requests

One of the most effective places for rate limiting is across all PHP handlers. A single aggressive bot hitting uncached PHP pages can exhaust your PHP-FPM pool and serve 502 errors to everyone:

location ~ \.php$ {
    limit_req zone=flood burst=5 nodelay;
    try_files $uri =404;
    include fastcgi_params;
    fastcgi_pass unix:/var/run/php/php8.5-fpm.sock;
}

burst=5 allows a brief spike of up to 5 requests above the rate limit before rejections start. A legitimate user loading a page with a few uncached assets will trigger several requests in quick succession — the burst accommodates that without opening the floodgates.


Rate limiting specific endpoints

Login forms, search boxes, API endpoints, and contact forms do more work per request than serving a cached page. Give them their own tighter limits:

# In http { } — a separate, stricter zone
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/m;

# In the server block
location = /wp-login.php {
    limit_req zone=login burst=2 nodelay;
    try_files $uri =404;
    include fastcgi_params;
    fastcgi_pass unix:/var/run/php/php8.5-fpm.sock;
}

One request per minute for a login page. An attacker with a password list gets two quick tries (the burst) and then a wall of 503s. Combine this with fail2ban watching your error log for repeated 503s and you've got a layered defense — the fail2ban + nftables guide covers the full integration.


Custom error responses

A 503 is technically correct (Service Unavailable), but you can also map it to 429 (Too Many Requests), which is the more specific HTTP status for rate limiting:

limit_req_status 429;

Or handle it with an error page:

error_page 503 =429 /rate_limited.html;
location = /rate_limited.html {
    root /var/www/mysite.com/errors;
    internal;
}

A note on testing

Tools like ab (ApacheBench), siege, or wrk can verify your rate limiting is working:

# 100 concurrent requests, 10 at a time
ab -n 100 -c 10 https://mysite.com/about.html

Check your access log afterward — you should see a mix of 200s and 503s (or 429s if you changed the status code).


What rate limiting won't do

limit_req protects against HTTP-level floods. It won't stop SYN floods, UDP amplification attacks, or volumetric DDoS that saturates your pipe before packets reach Nginx. For those, you need upstream protection — a CDN (Cloudflare, Fastly), a hardware firewall from your host, or kernel-level tools like nftables. Rate limiting in Nginx is one layer. It's a good one, but it's not the only one.


Where this fits

Rate limiting works best as part of a larger strategy:

  • The fail2ban + nftables guide covers reactive banning and crowd-sourced blacklists — rate limiting handles the flood, fail2ban bans the repeat offenders.
  • Preventing WordPress Search Overload covers the search-specific try_files vs rewrite phase issue that trips up rate limiting on WordPress search endpoints.
  • The WordPress on Nginx guide covers login-page rate limiting, concurrent PHP connection caps, and the full production Nginx configuration.
  • Securing Nginx and PHP covers the botstop pattern — adding limit_req to your deny blocks to throttle scanners between the first blocked request and fail2ban jail thresholds.

Full documentation: ngx_http_limit_req_module

Technical Audit Summary

This guide is maintained as part of a modular, SSL-first framework. The rate-limiting patterns described here have been verified against live traffic across multiple WordPress sites.

Last Audit: May 2026
Environment: Debian Trixie (13)
Nginx: 1.30.2
PHP-FPM: 8.5.6

Compatibility: The limit_req module is included in all standard Nginx builds. All patterns work on Nginx 1.18+.

2026-05-21: Production audit — synced stack versions (Nginx 1.30.1, PHP-FPM 8.5.6). Replaced parameter reference table with card layout for mobile readability. Added cross-links to fail2ban guide, search-overload article, WordPress on Nginx guide, and securing-nginx-php guide to show where rate limiting fits in a layered defense.