Building a Rate Limiter in PHP Using Redis to Prevent Abuse

Building a Rate Limiter in PHP Using Redis to Prevent Abuse

Learn how to build a rate limiter in PHP using Redis. Protect your application from abuse by limiting requests per user with INCR, EXPIRE, and sliding window techniques.

Introduction

Rate limiting is essential to prevent abuse, bot attacks, and excessive API requests. By implementing Redis-based rate limiting, you can:

  • Restrict API calls per user/IP within a time window.
  • Throttle abusive traffic without affecting legitimate users.
  • Ensure fair resource allocation across all users.

Redis provides fast in-memory counting, making it ideal for rate limiting with commands like:

  • INCR – Increments request count.
  • EXPIRE – Sets an expiration for the request counter.
  • ZADD / ZREMRANGEBYSCORE – Implements a sliding window rate limiter.

This guide covers:

  • Implementing fixed window and sliding window rate limiting in PHP
  • Using PHPRedis and Predis for Redis-based throttling
  • Best practices for scalable rate limiting

1. Why Use Redis for Rate Limiting?

Traditional PHP-based rate limiting methods use:

Session-based limits – Inefficient for APIs and distributed systems.
Database-based limits – Slow and causes performance bottlenecks.

Redis is fast and scalable for rate limiting because:

✅ Stores counters in-memory, reducing lookup time.
✅ Automatically expires old counters, freeing memory.
✅ Works well in distributed systems.

2. Implementing Fixed Window Rate Limiting with Redis

A fixed window approach limits requests within a defined time window.

Step 1: Connect to Redis

$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

Step 2: Define Rate Limit Parameters

$rateLimit = 10;  // Maximum 10 requests
$timeWindow = 60; // Time window (60 seconds)
$ip = $_SERVER['REMOTE_ADDR']; // User IP
$cacheKey = "rate_limit:$ip";

Step 3: Check Request Count

$requests = $redis->incr($cacheKey);

if ($requests == 1) {
    $redis->expire($cacheKey, $timeWindow);
}

if ($requests > $rateLimit) {
    http_response_code(429);
    die("Rate limit exceeded. Try again later.");
}

echo "Request allowed. Current count: $requests";

How It Works:

  • Each request increments the counter.
  • The first request sets an expiration on the counter.
  • If requests exceed the limit, return HTTP 429 (Too Many Requests).

3. Implementing Sliding Window Rate Limiting

A sliding window approach ensures more even distribution of requests over time.

Using ZADD and ZREMRANGEBYSCORE for Time-Based Sliding Window

Instead of resetting at a fixed time, Redis tracks individual request timestamps and removes old ones.

$limit = 10;  
$timeWindow = 60;  
$ip = $_SERVER['REMOTE_ADDR'];  
$cacheKey = "rate_limit:$ip";

$currentTime = microtime(true) * 1000; // Current time in milliseconds

// Remove outdated requests
$redis->zremrangebyscore($cacheKey, 0, $currentTime - ($timeWindow * 1000));

// Get current request count
$requestCount = $redis->zcard($cacheKey);

if ($requestCount >= $limit) {
    http_response_code(429);
    die("Rate limit exceeded.");
}

// Add the new request timestamp
$redis->zadd($cacheKey, $currentTime, $currentTime);

// Set expiration to auto-cleanup old data
$redis->expire($cacheKey, $timeWindow);

echo "Request allowed. Total requests in window: " . ($requestCount + 1);

How It Works:

  • Each request logs its timestamp in a Redis sorted set.
  • Expired timestamps are removed before checking the request count.
  • Only valid requests within the last X seconds are counted.

4. Rate Limiting API Endpoints

Middleware for Rate Limiting API Calls

function rateLimitMiddleware($limit, $timeWindow) {
    global $redis;

    $ip = $_SERVER['REMOTE_ADDR'];
    $cacheKey = "rate_limit:$ip";

    $requests = $redis->incr($cacheKey);

    if ($requests == 1) {
        $redis->expire($cacheKey, $timeWindow);
    }

    if ($requests > $limit) {
        http_response_code(429);
        die(json_encode(["error" => "Rate limit exceeded"]));
    }
}

// Apply rate limiter (5 requests per 30 seconds)
rateLimitMiddleware(5, 30);

echo json_encode(["message" => "API request successful"]);

5. Resetting Rate Limits Dynamically

Manually Reset User’s Rate Limit

$redis->del("rate_limit:$ip"); // Clears the rate limit for the user

Setting Different Limits for Different Users

$userType = "premium"; // Assume a user type
$rateLimits = ["free" => 10, "premium" => 50];

$limit = $rateLimits[$userType] ?? 10;
  • Free users get 10 requests per minute.
  • Premium users get 50 requests per minute.

6. Best Practices for Redis Rate Limiting

Use fixed windows for simple rate limits (e.g., login attempts).
Use sliding windows for API requests to ensure fairness.
Set appropriate expiration (EXPIRE) to free memory.
Adjust rate limits dynamically for different users or services.
Monitor Redis performance to track memory usage.

Conclusion

Redis-powered rate limiting ensures fair API usage, prevents abuse, and improves security.

This guide covered:

  • Fixed and sliding window rate limiting
  • Using INCR, EXPIRE, ZADD, and ZREMRANGEBYSCORE
  • Implementing rate limits in PHP APIs

By following these methods, you can protect your application while maintaining performance and scalability.

Leave a Reply