Rate Limiting and Abuse Control

Implementing rate limiting, request throttling, and abuse prevention measures to protect Multi Host deployments from misuse.

Updated August 2025

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:

  1. Monitor rejection rates for false positives
  2. Identify legitimate high-usage patterns
  3. Adjust limits or create exceptions as needed
  4. 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.