Setting Up Proxy Rate Limiting Zones to Thwart Forum Credential Stuffing

Community forums regularly face credential stuffing bots attempting rapid password guesses against login handlers. When automated tools swarm your sign-in forms, handling authentication scripts directly inside forum backend processes exhausts server memory and database connections. Placing rate limiting inside your front-facing proxy mitigates this abuse before traffic touches application runtimes. By implementing leaky bucket algorithms provided by your web server, administrators can restrict login attempts per client IP address while ensuring normal members can sign in without interruption.
Allocate Shared Memory Zones for Client Tracking
Tracking client request frequencies across an entire proxy process requires dedicated shared memory. Within NGINX, the Module ngx_http_limit_req_module (available since version 0.7.21) provides this tracking through shared memory zones that record states for specified keys.
To track incoming visitors accurately while conserving memory on your reverse proxy architecture, use the binary representation of client IP addresses:
- IPv4 client addresses tracked via $binary_remote_addr require 4 bytes of memory.
- IPv6 client addresses tracked via $binary_remote_addr consume 16 bytes of storage.
- Individual stored client states occupy 64 bytes on 32-bit systems and 128 bytes on 64-bit operating platforms.
- A 1 megabyte memory zone retains approximately 16 thousand states on 32-bit platforms or about 8 thousand states on 64-bit systems.
If allocated zone storage becomes exhausted during high traffic, the proxy removes the least recently used state to record newer requests. If a new state still cannot be stored following that removal, the proxy terminates the request with an error.
Define Authentication Rate Zones in the HTTP Block
Configuring limits begins inside the http block of your configuration using the directive limit_req_zone. This directive cannot reside directly inside individual server or location blocks; it must be declared at the http level so worker threads share access to the tracked memory space.
The directive syntax sets the key, zone name, storage allocation, and target processing rate. Declaring a 10 megabyte zone named login_limit sets aside enough space for tens of thousands of simultaneous IP entries:
limit_req_zone $binary_remote_addr zone=login_limit:10m rate=1r/s;
Under this definition, the proxy evaluates the client IP key and restricts average inbound frequency to 1 request per second (1r/s). If your forum authentication route requires stricter thresholds, the rate parameter also accepts requests per minute (r/m). For instance, setting a rate of 30r/m enforces an average allowance of a half-request per second. Since version 1.7.6, keys can combine text with variables, though empty key values are ignored and not accounted.
Attach Rate Limits to Forum Login Locations
Once your shared memory space is defined, attach the limit to specific forum authentication endpoints within your server or location blocks. Applying limits directly to login, registration, and administrative routes prevents credential stuffing without restricting community members reading threads or browsing search boards.
You activate enforcement by declaring the limit_req directive inside the target location block:
location /login/ { limit_req zone=login_limit; }
Directives set at higher levels are inherited if and only if there are no limit_req directives defined on the current configuration level. You can also layer multiple directives simultaneously. For example, an administrator can restrict individual IP addresses to 1r/s while simultaneously restricting total requests hitting the entire virtual server name to 10r/s using a secondary shared zone.
Manage Bursts and Request Delay Behavior
By default, the maximum burst size configured for a zone is equal to 0. Without an allowance for bursts, any client submitting two requests within a single second would encounter an immediate block. Legitimate forum members often submit assets or retry password entries quickly, making burst configurations essential.
The burst parameter establishes how many excessive requests can queue up while adhering to the leaky bucket processing rate:
location /login/ { limit_req zone=login_limit burst=5 nodelay; }
Under this rule, requests exceeding 1 request per second are accommodated up to a burst limit of 5. When excessive requests surpass the burst threshold, the proxy terminates the connection with an error. If you omit the nodelay flag, excessive requests are delayed so that processing matches the configured rate. The delay parameter (introduced in version 1.15.7) allows administrators to specify an exact boundary where excessive requests begin experiencing artificial delays, while its default value of 0 delays all excessive requests.
Configure Rejection Status Codes and Log Levels
When automated stuffing scripts exceed your burst allowance, the proxy rejects the incoming traffic. Reviewing rejection headers and event logs ensures you can audit attacks while protecting administrative accounts from brute-force penetration.
The default HTTP status returned to clients when a request is rejected is 503. If your community prefers a distinct code, adjust it across http, server, or location contexts using limit_req_status:
limit_req_status 503;limit_req_log_level warn;
The limit_req_status directive appeared in version 1.3.15. To monitor rejected login attempts in error logs, configure limit_req_log_level, which was introduced in version 0.8.18 and defaults to error. Available logging values include info, notice, warn, and error. The logging level for delayed requests is automatically one point lower than for refusals; for example, setting the refusal level to notice causes delayed requests to be recorded at the info level.
Verify Limits Using Dry Run Mode and Variables
Enforcing rate limits prematurely can accidentally block legitimate forum users if operational thresholds are configured too strictly. To observe how your community interacts with authentication endpoints before turning on active blocking, enable dry run testing.
The directive limit_req_dry_run (introduced in version 1.17.1) accepts on or off, defaulting to off:
limit_req_dry_run on;
In dry run mode, the proxy tracks excessive requests inside the shared memory zone as usual, but does not delay or reject inbound client traffic. Administrators can inspect the outcome using the embedded variable $limit_req_status (introduced in version 1.17.6). Detailed in the limit_req_zone directive reference, this variable records one of five precise values for every request: PASSED, DELAYED, REJECTED, DELAYED_DRY_RUN, or REJECTED_DRY_RUN. Reviewing these logged values allows you to verify that credential stuffers would be blocked while genuine forum logins pass safely.
Sources
Published October 30, 2026. No affiliate links on this page. Facts come from the sources linked above; check them for updates.