Laravel API Rate-Limiting: Sliding Windows &amp; Redis | Mohamed Said        [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://msaied.com) [ Home ](https://msaied.com) [ Projects ](https://msaied.com/projects) [ Articles  ](https://msaied.com/articles) [ Certificates ](https://msaied.com/certificates) [ Contact ](https://msaied.com#contact-section) 

       [  ](https://github.com/EG-Mohamed)       

 [ Home ](https://msaied.com) [ Projects ](https://msaied.com/projects) [ Articles ](https://msaied.com/articles) [ Certificates ](https://msaied.com/certificates) [ Contact ](https://msaied.com#contact-section) 

  [ home ](https://msaied.com)    [ articles ](https://msaied.com/articles)    API Rate-Limiting in Laravel: Sliding Windows, Per-Route Limiters, and Redis Precision        On this page       1. [  Beyond throttle:60,1 — Real Rate-Limiting in Laravel ](#beyond-codethrottle601code-real-rate-limiting-in-laravel)
2. [  Named Limiters in AppServiceProvider ](#named-limiters-in-codeappserviceprovidercode)
3. [  Multiple Limits: Burst + Sustained ](#multiple-limits-burst-sustained)
4. [  Sliding Window with Redis ZADD ](#sliding-window-with-redis-zadd)
5. [  Exposing Quota Headers ](#exposing-quota-headers)
6. [  Handling 429 Gracefully ](#handling-429-gracefully)
7. [  Key Takeaways ](#key-takeaways)

  ![API Rate-Limiting in Laravel: Sliding Windows, Per-Route Limiters, and Redis Precision](https://cdn.msaied.com/398/bb46e069aecd78f39c7ac31e2e1b13e2.png)

  #laravel   #api   #redis   #rate-limiting  

 API Rate-Limiting in Laravel: Sliding Windows, Per-Route Limiters, and Redis Precision 
========================================================================================

     9 Jul 2026      3 min read    ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said  

       Table of contents

1. [  01   Beyond throttle:60,1 — Real Rate-Limiting in Laravel  ](#beyond-codethrottle601code-real-rate-limiting-in-laravel)
2. [  02   Named Limiters in AppServiceProvider  ](#named-limiters-in-codeappserviceprovidercode)
3. [  03   Multiple Limits: Burst + Sustained  ](#multiple-limits-burst-sustained)
4. [  04   Sliding Window with Redis ZADD  ](#sliding-window-with-redis-zadd)
5. [  05   Exposing Quota Headers  ](#exposing-quota-headers)
6. [  06   Handling 429 Gracefully  ](#handling-429-gracefully)
7. [  07   Key Takeaways  ](#key-takeaways)

 Beyond `throttle:60,1` — Real Rate-Limiting in Laravel
------------------------------------------------------

The built-in `throttle` middleware is fine for simple cases, but the moment you need per-plan quotas, burst allowances, or sub-second precision you hit its limits fast. Laravel's `RateLimiter` facade, combined with Redis primitives, gives you everything you need without reaching for a third-party package.

---

Named Limiters in `AppServiceProvider`
--------------------------------------

Define limiters once, reference them everywhere.

```php
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;

public function boot(): void
{
    RateLimiter::for('api', function (Request $request) {
        $user = $request->user();

        return $user
            ? Limit::perMinute($user->plan->api_rpm)->by($user->id)
            : Limit::perMinute(20)->by($request->ip());
    });
}

```

Attach it in your route file:

```php
Route::middleware(['auth:sanctum', 'throttle:api'])
    ->group(base_path('routes/api.php'));

```

The `by()` key is the Redis key suffix. Using `$user->id` means every user gets an independent counter — no shared-bucket surprises.

---

Multiple Limits: Burst + Sustained
----------------------------------

Return an array to enforce both a burst cap and a sustained cap simultaneously:

```php
RateLimiter::for('ai-inference', function (Request $request) {
    return [
        Limit::perMinute(10)->by($request->user()->id),   // burst
        Limit::perDay(500)->by($request->user()->id),     // sustained
    ];
});

```

Laravel evaluates every limit in the array and returns `429` as soon as any one is exceeded. The `Retry-After` header reflects the shortest remaining window.

---

Sliding Window with Redis ZADD
------------------------------

Laravel's default limiter uses a fixed window (a counter that resets at a hard boundary). For APIs where clients batch requests at window edges, a **sliding window** is fairer.

Implement it with a sorted set:

```php
use Illuminate\Support\Facades\Redis;

final class SlidingWindowLimiter
{
    public function attempt(string $key, int $maxAttempts, int $windowSeconds): bool
    {
        $now = microtime(true);
        $windowStart = $now - $windowSeconds;

        Redis::pipeline(function ($pipe) use ($key, $now, $windowStart) {
            // Remove entries outside the window
            $pipe->zremrangebyscore($key, '-inf', $windowStart);
            // Add current request timestamp as both score and member
            $pipe->zadd($key, $now, uniqid('', true));
            // Expire the key slightly beyond the window
            $pipe->expire($key, (int) ceil($windowStart) + 10);
        });

        $count = Redis::zcard($key);

        return $count user()?->id ?? $request->ip();

    if (! $this->limiter->attempt($key, 30, 60)) {
        return response()->json(['message' => 'Too Many Requests'], 429);
    }

    return $next($request);
}

```

The pipeline keeps the three Redis commands atomic enough for most workloads. For strict atomicity under high concurrency, replace the pipeline with a Lua script.

---

Exposing Quota Headers
----------------------

Clients need visibility. Add headers in a response macro or middleware:

```php
$key = RateLimiter::key('api', $request);
$response->headers->set('X-RateLimit-Limit', 60);
$response->headers->set('X-RateLimit-Remaining', RateLimiter::remaining('api', $request));
$response->headers->set('X-RateLimit-Reset', RateLimiter::availableIn('api', $request));

```

`RateLimiter::remaining()` accepts the limiter name and the request, so it resolves the correct per-user key automatically.

---

Handling 429 Gracefully
-----------------------

Return a JSON body with a `retry_after` field so API clients can back off intelligently:

```php
// In app/Exceptions/Handler.php (Laravel 11+ bootstrap/app.php)
$exceptions->render(function (ThrottleRequestsException $e, Request $request) {
    return response()->json([
        'message' => 'Rate limit exceeded.',
        'retry_after' => $e->getHeaders()['Retry-After'] ?? null,
    ], 429, $e->getHeaders());
});

```

---

Key Takeaways
-------------

- **Named limiters** in `boot()` centralise quota logic and support dynamic per-user values.
- **Array limits** let you combine burst and sustained caps with zero extra code.
- **Sliding windows** via Redis sorted sets eliminate the fixed-window edge-burst problem.
- **Expose quota headers** (`X-RateLimit-*`) so clients can self-throttle before hitting 429.
- **Custom 429 responses** with `retry_after` make your API a good citizen for automated consumers.

 Found this useful?

          [  ](https://twitter.com/intent/tweet?url=https%3A%2F%2Fmsaied.com%2Farticles%2Fapi-rate-limiting-in-laravel-sliding-windows-per-route-limiters-and-redis-precision&text=API+Rate-Limiting+in+Laravel%3A+Sliding+Windows%2C+Per-Route+Limiters%2C+and+Redis+Precision) [  ](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fmsaied.com%2Farticles%2Fapi-rate-limiting-in-laravel-sliding-windows-per-route-limiters-and-redis-precision) 

 Frequently Asked Questions 
----------------------------

  3 questions  

     Q01  What is the difference between a fixed window and a sliding window rate limiter?        A fixed window resets its counter at a hard time boundary (e.g., every full minute). A sliding window tracks requests within the last N seconds relative to *now*, so a burst at the boundary of two fixed windows is still caught. Sliding windows are fairer but require a sorted set in Redis rather than a simple counter. 

      Q02  Can I use named limiters with Sanctum or Passport token scopes?        Yes. Inside the named limiter closure you have full access to the request, including `$request-&gt;user()` and any token abilities. You can branch on `$request-&gt;user()-&gt;tokenCan('admin')` or check a plan attribute to return different `Limit` instances per token scope. 

      Q03  Is the Redis pipeline approach atomic enough for production?        For most APIs, yes — the three commands (ZREMRANGEBYSCORE, ZADD, EXPIRE) execute back-to-back with no interleaving from other clients in a pipeline. If you need strict atomicity under very high concurrency on a single key, replace the pipeline with a Lua script executed via `Redis::eval()`, which runs atomically on the Redis server. 

  Continue reading

 More Articles 
---------------

 [ View all    ](https://msaied.com/articles) 

 [ ![FrankenPHP, OPcache JIT, and Preloading: Squeezing Real Throughput from Laravel](https://cdn.msaied.com/585/7dc4b3832f33e2c630172d5b5dd24ac1.png) laravel frankenphp performance 

### FrankenPHP, OPcache JIT, and Preloading: Squeezing Real Throughput from Laravel

A practical guide to deploying Laravel under FrankenPHP with OPcache JIT and preloading enabled — covering wor...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 23 Aug 2026     1 min read  

  Read    

 ](https://msaied.com/articles/frankenphp-opcache-jit-and-preloading-squeezing-real-throughput-from-laravel-3) [ ![Streaming AI Responses in Laravel: Token Budgets, Structured Output, and Agent Contracts](https://cdn.msaied.com/584/3ffe135721c65ab3f9b40401dc3c41de.png) laravel ai llm 

### Streaming AI Responses in Laravel: Token Budgets, Structured Output, and Agent Contracts

Learn how to stream LLM responses in Laravel, enforce token budgets, and lock structured output to typed PHP c...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 23 Aug 2026     4 min read  

  Read    

 ](https://msaied.com/articles/streaming-ai-responses-in-laravel-token-budgets-structured-output-and-agent-contracts) [ ![Typed PHP 8.3 Enums as Eloquent Casts, Route Parameters, and Validation Rules](https://cdn.msaied.com/583/7301900aac0f2ec5d1347df11ce92188.png) laravel php8.3 enums 

### Typed PHP 8.3 Enums as Eloquent Casts, Route Parameters, and Validation Rules

Go beyond basic enum casting. Learn how to wire PHP 8.3 backed enums into Eloquent, route model binding, and c...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 23 Aug 2026     1 min read  

  Read    

 ](https://msaied.com/articles/typed-php-83-enums-as-eloquent-casts-route-parameters-and-validation-rules) 

   [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://msaied.com)Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

Explore

- [Home](https://msaied.com)
- [Projects](https://msaied.com/projects)
- [Articles](https://msaied.com/articles)
- [Certificates](https://msaied.com/certificates)
- [Contact](https://msaied.com#contact-section)

Connect

- [   hello@msaied.com ](mailto:hello@msaied.com)
- [   +20 109 461 9204 ](tel:+201094619204)

© 2026 Mohamed Said. All rights reserved.

 [  ](https://github.com/EG-Mohamed) [  ](https://www.linkedin.com/in/msaiedm/) [  ](https://wa.me/201094619204) [  ](mailto:hello@msaied.com) [  ](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
