Introduction
Rate limiting protects your app from abuse: a brute-force login, a runaway script, or a cheap-but-popular public endpoint hammered by bots. Laravel bundles a flexible rate limiter you can apply to any route with one call.
Key Concepts
throttlemiddleware: Limits how many requests a given client can make in a time window.- Named limiter: A reusable rate limit defined in a service provider with
RateLimiter::for(). - Key resolver: How Laravel identifies 'the same client' — usually the authenticated user ID or the IP address.
Limit::perMinute(n)->by(...): The fluent API that builds a single limit bucket.
Real World Context
Every public login form should be rate limited — without it, attackers can try thousands of passwords per second. APIs use rate limits to enforce fair usage across customers and protect upstream services.
Deep Dive
The simplest form hard-codes the limit on the route:
php// 60 requests per 1 minute, keyed by the client automatically Route::get('/api/data', DataController::class) ->middleware('throttle:60,1');
Notice how concise the inline form is. For anything reusable, define a named limiter in App\Providers\AppServiceProvider::boot():
phpuse Illuminate\Cache\RateLimiting\Limit; use Illuminate\Http\Request; use Illuminate\Support\Facades\RateLimiter; public function boot(): void { RateLimiter::for('api', function (Request $request) { return Limit::perMinute(60) ->by($request->user()?->id ?: $request->ip()); }); RateLimiter::for('uploads', function (Request $request) { return $request->user()?->isPremium() ? Limit::none() : Limit::perMinute(10)->by($request->user()->id); }); }
Now any route can reference the limiter by name:
phpRoute::middleware('throttle:api')->group(function () { Route::get('/api/posts', [PostController::class, 'index']); });
When a client exceeds the limit, Laravel responds with HTTP 429 Too Many Requests and includes Retry-After, X-RateLimit-Limit, and X-RateLimit-Remaining headers so well-behaved clients can back off.
Common Pitfalls
- Keying only by IP behind a proxy — If your app runs behind Cloudflare or an ALB, every request looks like it comes from the proxy. Configure
TrustProxiesmiddleware so$request->ip()returns the real client. - Forgetting premium users — Rate limits should bypass trusted partners. Return
Limit::none()for accounts that should not be throttled.
Best Practices
- Name your limiters — Inline
throttle:60,1works but scatters policy through your routes. Named limiters centralise the rules. - Key by user when authenticated, IP when not — Anonymous traffic shares a pool; authenticated traffic deserves its own budget.
Summary
- Apply
throttle:max,minutesfor quick inline limits. - Define
RateLimiter::for('name', ...)for reusable rules. - Key limits by
user()->id ?: ip()so authenticated users get their own budget. - Laravel returns HTTP 429 with rate-limit headers when the limit is exceeded.
Code Examples
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
// In AppServiceProvider::boot()
RateLimiter::for('login', function (Request $request) {
return Limit::perMinute(5)
->by($request->input('email') . '|' . $request->ip());
});
// In routes/web.php
Route::post('/login', [LoginController::class, 'store'])
->middleware('throttle:login');