Rate Limiting and Abuse Control
Implementing rate limiting, request throttling, and abuse prevention measures to protect Multi Host deployments from misuse.
Public-facing upload services attract abuse ranging from spam to denial-of-service attacks. Rate limiting restricts how quickly users can consume resources, protecting service availability for legitimate users.
Understanding Rate Limiting
Rate limiting caps the number of requests or actions allowed within a time window. When limits are exceeded, subsequent requests are rejected or delayed until the window resets.
Key concepts:
- Window: The time period over which requests are counted (e.g., 1 minute)
- Limit: Maximum requests allowed per window (e.g., 10 requests)
- Identifier: What's being limited (user account, IP address, API key)
- Response: What happens when limit is exceeded (reject, delay, warn)
Different actions may have different limits—uploads might allow 10 per minute while API calls allow 60.
Configuration Basics
Enable rate limiting in Multi Host:
$config['rate_limit_enabled'] = true;
// Upload limits
$config['rate_limit_uploads'] = 10; // uploads per window
$config['rate_limit_upload_window'] = 60; // 60 seconds
// API request limits
$config['rate_limit_api'] = 60;
$config['rate_limit_api_window'] = 60;
// Authentication limits
$config['rate_limit_login'] = 5;
$config['rate_limit_login_window'] = 300; // 5 minutes
These settings establish baseline protection. Adjust based on your traffic patterns and user expectations.
Limiting Strategies
Per-User Limits
Authenticated users are limited by their account:
$config['rate_limit_by_user'] = true;
Per-user limits are the most accurate—they track actual accounts and can be customised per user or group.
Per-IP Limits
Anonymous requests and additional attack protection use IP-based limits:
$config['rate_limit_by_ip'] = true;
$config['rate_limit_trust_proxy'] = true; // Trust X-Forwarded-For
$config['rate_limit_trusted_proxies'] = ['10.0.0.0/8'];
IP limits catch unauthenticated abuse and provide defence against account-based attacks where attackers create many accounts.
Important: If behind a proxy or CDN, configure trusted proxies correctly. Without this, all requests appear to come from the proxy IP.
Combined Limits
Apply both user and IP limits together:
$config['rate_limit_by_user'] = true;
$config['rate_limit_by_ip'] = true;
$config['rate_limit_require_both'] = false; // Either limit can reject
Combined limiting prevents both high-volume single accounts and distributed attacks across many IPs.
Implementing Rate Limiting
Application-Level Implementation
Rate limiting within the application provides the most control:
class RateLimiter {
public function check($identifier, $action) {
$key = "ratelimit:{$action}:{$identifier}";
$window = $this->config["rate_limit_{$action}_window"];
$limit = $this->config["rate_limit_{$action}"];
$current = $this->cache->get($key, 0);
if ($current >= $limit) {
throw new RateLimitException("Rate limit exceeded");
}
$this->cache->increment($key, 1, $window);
return true;
}
}
Use fast storage (Redis, Memcached) for rate limit counters. Database storage creates bottlenecks under heavy load.
Web Server Level
Configure rate limiting in Nginx:
limit_req_zone $binary_remote_addr zone=uploads:10m rate=10r/m;
limit_req_zone $binary_remote_addr zone=api:10m rate=1r/s;
server {
location /upload {
limit_req zone=uploads burst=5 nodelay;
}
location /api {
limit_req zone=api burst=10;
}
}
Web server limiting happens before PHP processes requests, reducing load from attacks.
CDN/Edge Level
Cloudflare rate limiting and similar CDN features block abuse at the edge:
- Advantage: Attacks never reach your origin
- Limitation: Less visibility into user context
Combine edge limiting with application limiting for defence in depth.
Handling Rate Limit Responses
Rejection Response
Return appropriate HTTP status codes:
// HTTP 429 Too Many Requests
http_response_code(429);
header('Retry-After: 60');
header('X-RateLimit-Limit: 10');
header('X-RateLimit-Remaining: 0');
header('X-RateLimit-Reset: ' . (time() + 60));
Include headers that help legitimate clients understand and respect limits.
Graceful Degradation
For some actions, slow down rather than reject:
if ($requests_this_minute > $soft_limit) {
sleep(min($requests_this_minute - $soft_limit, 5));
}
Throttling maintains service while discouraging rapid requests.
User Communication
Inform users clearly when limited:
{
"error": "rate_limit_exceeded",
"message": "Upload limit reached. Please wait 60 seconds.",
"retry_after": 60
}
Avoid revealing exact limits in error messages—attackers use this information to optimise their attacks.
Beyond Basic Rate Limiting
Sliding Windows
Fixed windows create boundary problems—a user could make 10 requests at :59 and 10 more at :00, effectively 20 per minute.
Sliding windows count requests within the past N seconds from the current moment:
public function slidingWindowCheck($identifier, $action) {
$window = $this->config["rate_limit_{$action}_window"];
$limit = $this->config["rate_limit_{$action}"];
$now = time();
$key = "ratelimit:{$action}:{$identifier}";
// Remove expired timestamps
$this->redis->zremrangebyscore($key, 0, $now - $window);
// Count recent requests
$count = $this->redis->zcard($key);
if ($count >= $limit) {
return false;
}
// Add current request
$this->redis->zadd($key, $now, uniqid());
$this->redis->expire($key, $window);
return true;
}
Token Bucket Algorithm
Token bucket allows bursts while maintaining average rate:
class TokenBucket {
public function consume($identifier, $tokens = 1) {
$bucket = $this->getBucket($identifier);
// Refill tokens based on time passed
$now = microtime(true);
$elapsed = $now - $bucket['last_refill'];
$refill = $elapsed * $this->refill_rate;
$bucket['tokens'] = min($this->capacity, $bucket['tokens'] + $refill);
$bucket['last_refill'] = $now;
if ($bucket['tokens'] >= $tokens) {
$bucket['tokens'] -= $tokens;
$this->saveBucket($identifier, $bucket);
return true;
}
return false;
}
}
Token bucket works well for APIs where bursts are acceptable but sustained high rates are not.
Abuse Detection
Rate limiting is reactive—detecting abuse patterns enables proactive response.
Behavioural Analysis
Look for patterns beyond simple volume:
- Upload patterns: Many small files rapidly, or unusual file types
- Access patterns: Sequential URL enumeration, scraping behaviour
- Timing patterns: Automated cadence unlike human behaviour
$config['abuse_detection'] = true;
$config['flag_sequential_access'] = true;
$config['flag_unusual_timing'] = true;
Reputation Scoring
Build reputation scores combining factors:
- Account age
- Previous rate limit violations
- Content quality (moderation flags)
- Email domain (disposable vs legitimate)
- IP reputation (VPN, known proxy, datacenter)
Lower reputation accounts face stricter limits:
$multiplier = $this->getReputationMultiplier($user);
$effective_limit = $base_limit * $multiplier;
Automated Response
Trigger automatic responses to abuse detection:
$config['auto_ban_threshold'] = 10; // violations before ban
$config['auto_ban_duration'] = 86400; // 24 hour ban
$config['escalating_bans'] = true; // longer bans for repeats
Balance automation with human review—false positives damage legitimate user experience.
Operational Considerations
Monitoring
Track rate limiting metrics:
- Rejection rate by endpoint
- Users hitting limits (legitimate vs suspicious)
- Limit violations over time (trends)
Alerting thresholds help identify attacks and misconfigured limits.
Limit Tuning
Set initial limits conservatively, then adjust based on data:
- Monitor rejection rates for false positives
- Identify legitimate high-usage patterns
- Adjust limits or create exceptions as needed
- Review periodically as usage patterns change
Whitelist Management
Some users need higher limits:
$config['rate_limit_whitelist'] = [
'users' => [123, 456], // specific user IDs
'ips' => ['192.168.1.100'], // specific IPs
'groups' => ['premium'] // user groups
];
Document whitelist criteria and review periodically.
Frequently Asked Questions
Start conservatively: 10-20 uploads per minute, 60 API requests per minute, 5 login attempts per 5 minutes. Monitor rejection rates and adjust based on legitimate usage patterns.