Laravel Rate Limiting & Throttling Explained: Token Bucket, Sliding Window & Real-World Use Cases
Most developers treat rate limiting like a checkbox. That’s lazy thinking. If you're building serious products — especially APIs, SaaS, or fintech — rate limiting is not optional. It’s infrastructure protection. Let’s go deep.
- Laravel
- Rate Limiting
- API Security
- Throttling
What Is Rate Limiting (And Why It Actually Matters)
Rate limiting controls how many requests a client can make in a given time window. Without it:
- Bots will hammer your API
- Attackers will brute force login
- Scrapers will steal your data
- One bad client can overload your entire server
Laravel provides built-in throttling via middleware and a flexible RateLimiter class. But understanding the algorithms behind it makes you better than 90% of developers.
How Laravel Implements Rate Limiting
Laravel uses:
Illuminate\Support\Facades\RateLimiter
Under the hood, it relies on:
- Cache (Redis recommended in production)
- Atomic operations
- Time-based decay
The default algorithm behaves like a fixed window counter, but you can implement:
- Token Bucket
- Sliding Window
- Dynamic per-user limits
- Role-based limits
- IP-based or API-key-based limits
Fixed Window (Default Laravel Behavior)
How It Works
Example: 60 requests per minute.
- Window starts at 10:00:00
- User makes 60 requests → allowed
- At 10:00:59 → blocked
- At 10:01:00 → counter resets
Problem: It allows bursts at boundary edges. Example: 60 requests at 10:00:59 and 60 more at 10:01:01 → 120 requests in 2 seconds.
Token Bucket Algorithm
Concept
Think of it like a bucket holding tokens.
- Bucket capacity = max requests
- Tokens refill gradually
- Each request consumes 1 token
- If no tokens → request denied
Why It's Better:
- Allows small bursts
- Smooth traffic control
- More realistic control mechanism
Implementing Token Bucket in Laravel
Laravel doesn’t natively provide full token bucket logic — so we simulate it using Redis + RateLimiter.
Step 1: Create Custom Middleware
php artisan make:middleware TokenBucketLimiter
Step 2: Middleware Code
ip();
$currentTime = time();
$bucket = Cache::get($key, [
'tokens' => $this->capacity,
'last_refill' => $currentTime
]);
// Calculate tokens to refill
$elapsed = $currentTime - $bucket['last_refill'];
$refill = $elapsed * $this->refillRate;
$bucket['tokens'] = min(
$this->capacity,
$bucket['tokens'] + $refill
);
$bucket['last_refill'] = $currentTime;
if ($bucket['tokens'] < 1) {
return response()->json([
'message' => 'Too many requests.'
], 429);
}
$bucket['tokens'] -= 1;
Cache::put($key, $bucket, 60);
return $next($request);
}
}
Sliding Window Algorithm
Concept
Instead of resetting at fixed intervals, sliding window:
- Tracks requests over rolling time window
- Calculates request count in last X seconds dynamically
Example: Limit = 60 per minute. At 10:00:30 → system checks requests from 9:59:30 to 10:00:30.
Implementing Sliding Window in Laravel
We use Redis sorted sets for accurate timestamp tracking.
ip();
$now = time();
// Remove old timestamps
Redis::zremrangebyscore($key, 0, $now - $this->window);
// Count current requests
$count = Redis::zcard($key);
if ($count >= $this->limit) {
return response()->json([
'message' => 'Rate limit exceeded.'
], 429);
}
// Add new request
Redis::zadd($key, $now, uniqid());
Redis::expire($key, $this->window);
return $next($request);
}
}
Using Laravel’s Built-In RateLimiter Properly
Most devs stop here:
Route::middleware('throttle:60,1')->group(function () {
Route::get('/api/data', [DataController::class, 'index']);
});
But Laravel allows advanced logic:
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Http\Request;
public function boot()
{
RateLimiter::for('api', function (Request $request) {
if ($request->user()?->isPremium()) {
return Limit::perMinute(120);
}
return Limit::perMinute(60)
->by($request->user()?->id ?: $request->ip());
});
}
Real-World Use Cases
1. Login Protection
RateLimiter::for('login', function (Request $request) {
return Limit::perMinute(5)
->by($request->ip());
});
Stops brute force attacks.
2. OTP Verification Endpoint
- 3 attempts per 10 minutes
- Prevents SMS abuse
- Saves money
3. Public API SaaS Model
- Free tier → 1000/day
- Pro → 10,000/day
- Enterprise → Custom
Dynamic rate limiting = monetization tool.
4. Preventing Scraping
- Limit by IP
- Limit by API key
- Limit by user agent fingerprint
Scrapers die quickly when you do this properly.
Redis vs Database vs File Cache
If you’re using file cache in production for rate limiting — you’re building a toy.
Use Redis.
- Atomic increments
- Distributed systems safe
- Microsecond precision
- High concurrency handling
Production stack should be:
Laravel + Redis + Horizon
Common Developer Mistakes
- Only using default throttle
- Not differentiating premium users
- Not rate limiting login
- Not rate limiting expensive endpoints
- Forgetting distributed systems edge cases
- Using shared IP logic blindly (NAT problems)
Brutal Reality
If your API can be taken down by:
ab -n 5000 -c 100 http://yourapi.com
You're not production-ready.
Rate limiting is not decoration. It’s survival.
Final Thoughts
If you're serious about building scalable Laravel systems:
- Understand algorithms
- Choose the correct one
- Use Redis
- Customize per user
- Think monetization + protection
Average developers add throttle:60,1. Serious engineers design traffic control systems.
Now ask yourself honestly — which one are you becoming?
