The limit_req
directive in Nginx provides powerful request rate limiting capabilities, but many developers struggle when they need to implement more granular controls based on IP addresses. The standard implementation applies rate limits globally, which doesn't accommodate scenarios where you need to whitelist certain IPs or apply custom rate limits to specific addresses.
Many developers first attempt to solve this with conditional statements like:
if ($remote_addr = "1.2.3.4") {
# This won't work for rate limiting!
limit_req zone=mylimit burst=5 nodelay;
}
Nginx explicitly states in its documentation that limit_req
cannot be used inside if
blocks in location contexts. This limitation requires a different approach.
The proper way to implement IP-based rate limiting variations is through Nginx's geo
module. Here's a complete implementation:
http {
geo $rate_limit_key {
default $binary_remote_addr;
1.2.3.4/32 ""; # Whitelist this IP
5.6.7.8/32 "slow"; # Special rate for this IP
}
map $rate_limit_key $limit {
"" ""; # No limit
"slow" $binary_remote_addr;
default $binary_remote_addr;
}
limit_req_zone $limit zone=standard:10m rate=10r/s;
limit_req_zone $limit zone=slow:10m rate=1r/s;
server {
location / {
limit_req zone=standard burst=20 nodelay;
if ($rate_limit_key = "slow") {
limit_req zone=slow burst=5 nodelay;
}
}
}
}
For simpler cases, you can use multiple location blocks with different rate limits:
server {
location / {
limit_req zone=standard burst=20 nodelay;
}
location @whitelist {
# No rate limiting
}
location @slowlist {
limit_req zone=slow burst=5 nodelay;
}
}
Then use a map to route requests:
map $remote_addr $ratelimit_group {
default "standard";
1.2.3.4 "whitelist";
5.6.7.8 "slowlist";
}
When implementing IP-based rate limiting:
- The
geo
module approach is more efficient for large IP lists - Consider using
$binary_remote_addr
instead of$remote_addr
to save memory - Monitor your shared memory zone usage (
zone=standard:10m
)
After implementing, test with different IPs using curl:
# Test whitelisted IP
curl -H "X-Forwarded-For: 1.2.3.4" http://yourserver.com
# Test slow-limited IP
curl -H "X-Forwarded-For: 5.6.7.8" http://yourserver.com
# Test default IP
curl http://yourserver.com
When implementing rate limiting in NGINX, we often need differential treatment for specific IP addresses. The standard limit_req
directive applies globally, but production environments frequently require:
- Complete whitelisting for trusted IPs
- Custom rate limits for specific IP ranges
- Different thresholds for API clients vs regular users
The most efficient approach uses NGINX's geo
module to classify IPs before applying rate limits:
geo $rate_limit_key {
default $binary_remote_addr;
192.168.1.0/24 "";
203.0.113.42 "slow";
198.51.100.0/24 "strict";
}
map $rate_limit_key $limit {
"" "";
"slow" "zone=slow:10m rate=1r/s";
"strict" "zone=strict:10m rate=5r/m";
default "zone=global:10m rate=10r/s";
}
server {
location / {
limit_req $limit;
# Your regular config
}
}
For simpler cases with few IPs, you can use separate location blocks:
server {
location / {
limit_req zone=global burst=20 nodelay;
# Whitelisted IPs
if ($remote_addr ~ "^(192\.168\.1\.100|203\.0\.113\.42)$") {
set $limit_req "";
}
# Strictly limited IPs
if ($remote_addr ~ "^198\.51\.100\.(25[0-5]|2[0-4][0-9])$") {
limit_req zone=strict burst=1;
}
}
}
For API clients, consider adding headers to communicate rate limits:
limit_req_status 429;
limit_req zone=global burst=20 nodelay;
add_header X-RateLimit-Limit "10r/s" always;
add_header X-RateLimit-Remaining $limit_req remaining always;
Verify your configuration with:
nginx -t
for syntax validation- Tail error logs:
tail -f /var/log/nginx/error.log
- Test with curl:
curl -I http://yourserver.com