Rate Limiting: Request Rate Control
Rate Limiting restricts the number of requests processed per unit time, making it a core security feature for defending against DDoS attacks, brute force attacks, and web scraping.
Nginx Rate Limitingβ
Nginx provides two types of limits: limit_req (request rate) and limit_conn (concurrent connections).
limit_req_zone β Request Rate Limitingβ
# /etc/nginx/nginx.conf β http block
http {
# Define request rate limit zones
# key: $binary_remote_addr = client IP (binary form, memory-efficient)
# zone=api_limit:10m = zone name:memory size (10MB β 160,000 IPs)
# rate=10r/s = allow 10 requests per second
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login_limit:10m rate=5r/m; # 5 per minute
limit_req_zone $binary_remote_addr zone=general:10m rate=30r/s;
# Status code returned when limit is exceeded (default: 503)
limit_req_status 429; # Too Many Requests
limit_conn_status 429;
}
server {
# General pages: 30 requests per second per IP
location / {
limit_req zone=general burst=50 nodelay;
proxy_pass http://backend;
}
# API: 10 requests per second per IP, burst of 20
location /api/ {
limit_req zone=api_limit burst=20 nodelay;
proxy_pass http://backend;
}
# Login: 5 requests per minute per IP
location /api/auth/login {
limit_req zone=login_limit burst=3 nodelay;
proxy_pass http://backend;
}
}
Understanding burst and nodelay:
With rate=10r/s burst=20 nodelay:
βββββββββββββββββββββββββββββββββββββββββ
Process up to 20 requests instantly (nodelay)
Then process at 10 requests per second
Return 429 when burst is exceeded
Without nodelay: queue requests within burst at the rate speed (delayed processing)
limit_conn β Concurrent Connection Limitβ
http {
# Concurrent connection limit zone
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
}
server {
location / {
# Maximum 10 concurrent connections per IP
limit_conn conn_limit 10;
proxy_pass http://backend;
}
# File downloads: connection + bandwidth limit
location /downloads/ {
limit_conn conn_limit 3;
limit_rate 1m; # 1MB/s per connection bandwidth limit
limit_rate_after 10m; # First 10MB unlimited, then limited
alias /var/www/downloads/;
}
}
Exclude Whitelisted IPs from Rate Limitingβ
http {
# Exclude internal networks from rate limiting
geo $limit {
default $binary_remote_addr;
127.0.0.1 ""; # Empty string β rate limit not applied
10.0.0.0/8 "";
192.168.0.0/16 "";
}
limit_req_zone $limit zone=api_limit:10m rate=10r/s;
}
Apache Rate Limitingβ
mod_ratelimitβ
sudo a2enmod ratelimit
<Location "/api/">
# Transfer rate limit (bytes/second)
SetOutputFilter RATE_LIMIT
SetEnv rate-limit 400 # 400KB/s
</Location>
mod_evasive β HTTP Flood Defenseβ
sudo apt install libapache2-mod-evasive
sudo a2enmod evasive
# /etc/apache2/mods-available/evasive.conf
<IfModule mod_evasive20.c>
# Same-page request threshold: number of requests to the same URI within 2 seconds
DOSPageCount 5
DOSSiteCount 50
# Blacklist retention time when threshold is exceeded (seconds)
DOSPageInterval 2
DOSSiteInterval 1
DOSBlockingPeriod 10
# Email notification on block
DOSEmailNotify admin@example.com
# Whitelist IPs
DOSWhitelist 127.0.0.1
DOSWhitelist 10.0.0.*
# Log directory
DOSLogDir /var/log/mod_evasive
</IfModule>
Rate Limiting in Spring Bootβ
Nginx rate limiting applies to all requests, but you can also implement additional controls at the application level.
// Using the Bucket4j library (add dependency)
// implementation 'com.bucket4j:bucket4j-core:8.x.x'
@RestController
@RequestMapping("/api")
public class ApiController {
private final Map<String, Bucket> buckets = new ConcurrentHashMap<>();
private Bucket getBucket(String ip) {
return buckets.computeIfAbsent(ip, k ->
Bucket.builder()
.addLimit(Bandwidth.classic(10, Refill.greedy(10, Duration.ofSeconds(1))))
.build()
);
}
@GetMapping("/products")
public ResponseEntity<?> getProducts(HttpServletRequest request) {
String ip = request.getRemoteAddr();
Bucket bucket = getBucket(ip);
if (bucket.tryConsume(1)) {
return ResponseEntity.ok(productService.findAll());
}
return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS)
.header("X-Rate-Limit-Retry-After-Seconds", "1")
.body("Too many requests");
}
}
Customizing Rate Limit Responsesβ
# Add Retry-After header to 429 responses
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
server {
location /api/ {
limit_req zone=api_limit burst=20 nodelay;
# Customize 429 response
error_page 429 @rate_limited;
}
location @rate_limited {
add_header Retry-After 1 always;
add_header Content-Type "application/json" always;
return 429 '{"error":"Too Many Requests","retryAfter":1}';
}
}
Rate Limiting Monitoringβ
# Check frequency of 429 errors
grep " 429 " /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -20
# Enable rate limit logging
http {
log_format rate_log '$remote_addr [$time_local] "$request" $status '
'limit_req=$limit_req_status';
# $limit_req_status: PASSED, DELAYED, REJECTED, DELAYED_DRY_RUN, REJECTED_DRY_RUN
access_log /var/log/nginx/rate.log rate_log;
}
# Real-time monitoring of blocked IPs
tail -f /var/log/nginx/access.log | grep " 429 "
Recommended Rate Limit Settings by Endpointβ
| Endpoint | zone | rate | burst | Notes |
|---|---|---|---|---|
| General pages | general | 30r/s | 50 | Set higher behind CDN |
| API | api_limit | 10r/s | 20 | Adjust to service characteristics |
| Login | login_limit | 5r/m | 3 | Brute force defense |
| Search | search_limit | 5r/s | 10 | Consider DB load |
| File downloads | download | 2r/s | 5 | Limit with bandwidth |