DEV Community

Cover image for Stop API Floods: Redis Sliding Window Rate Limits ⚡
Prajapati Paresh
Prajapati Paresh

Posted on • Originally published at smarttechdevs.in

Stop API Floods: Redis Sliding Window Rate Limits ⚡

The Fixed Window Vulnerability

When protecting your SaaS API at Smart Tech Devs, rate limiting is your first line of defense against DDoS attacks and brute-force scraping. The default approach in most frameworks is the Fixed Window algorithm (e.g., allowing 60 requests per minute).

Here is the hidden architectural vulnerability: A malicious script can send 60 requests at 11:59:59 AM, and another 60 requests at 12:00:01 PM. Because the "minute window" reset at exactly 12:00:00, the server just absorbed 120 requests in 2 seconds. This burst traffic bypasses your intended limits, overwhelming your database connection pool and crashing your API. To mathematically guarantee smooth traffic flow, you must implement a Sliding Window algorithm.

The Solution: Redis Sorted Sets (ZSET)

A Sliding Window algorithm doesn't reset at the top of the minute. Instead, it looks back exactly 60 seconds from the current microsecond. If there are more than 60 requests in that dynamic, rolling timeframe, it blocks the request.

We can build this with extreme performance using a Redis Sorted Set (ZSET). We use the current Unix timestamp as the "score", allowing us to instantly drop old requests and count recent ones atomically.

Architecting the Sliding Window Middleware

Let's build a custom Laravel middleware that executes this sliding window check directly in the Redis C-level engine before the request ever touches our database.


namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Redis;
use Symfony\Component\HttpFoundation\Response;

class SlidingWindowRateLimiter
{
    public function handle(Request $request, Closure $next, int $limit = 60, int $windowSeconds = 60)
    {
        $ip = $request->ip();
        $redisKey = "rate_limit:sliding:{$ip}";
        
        $now = microtime(true);
        $windowStart = $now - $windowSeconds;

        // 1. Remove all request timestamps older than our 60-second window
        Redis::zremrangebyscore($redisKey, '-inf', $windowStart);

        // 2. Count how many requests occurred in the current rolling window
        $currentRequests = Redis::zcard($redisKey);

        if ($currentRequests >= $limit) {
            return response()->json([
                'error' => 'Too Many Requests.',
                'retry_after' => $windowSeconds
            ], Response::HTTP_TOO_MANY_REQUESTS);
        }

        // 3. Add the current request timestamp to the Sorted Set
        // We use $now as BOTH the score (for sorting) and the member value
        Redis::zadd($redisKey, $now, $now);

        // 4. Set an expiry on the key to prevent memory leaks if the user leaves
        Redis::expire($redisKey, $windowSeconds);

        return $next($request);
    }
}

The Engineering ROI

By migrating from Fixed Window to a Redis Sliding Window, you completely eliminate the "boundary burst" vulnerability. Your API traffic is forced into a smooth, continuous flow, protecting your backend infrastructure from microsecond DDoS spikes while keeping your latency incredibly low (sub-millisecond execution times in Redis).

Top comments (0)