
August 17, 2026
11 min read
By Kokil Thapa | Last reviewed: September 2026
A single aggressive scraper can starve your database pool before you notice anything wrong. Laravel API rate limiting stops that by capping requests per user, IP, or API key inside a sliding time window. Laravel ships a built-in throttle middleware backed by cache counters. You define policies once and attach them to routes. For any public API — mobile apps, partner integrations, or SaaS endpoints — this is baseline infrastructure, not an optional extra. Start with Laravel API best practices to see where throttling fits alongside auth, validation, and versioning.
bootstrap/app.php via RateLimiter::for(), applied with throttle:api middleware. Store counters in Redis for atomic increments, partition keys with by(), and return HTTP 429 with Retry-After headers when limits are exceeded.The framework increments a counter on each request and compares it against your configured ceiling. Exceed the limit and Laravel returns HTTP 429 before your controller runs. That saves CPU, database connections, and queue workers from abuse you never asked for. On production Laravel applications I maintain, throttling sits alongside Sanctum or Passport authentication as the first line of defense after TLS termination.
How do you configure Laravel API rate limiting in Laravel 13?
Since Laravel 11, limiter definitions live in bootstrap/app.php instead of RouteServiceProvider. Laravel 13 keeps the same pattern. Laravel 12 works identically and remains supported through February 2027. Named limiters decouple policy from route files. You reuse the same throttle profile across API routes, webhooks, and even queued jobs that call external services.
Define named limiters in bootstrap/app.php
The most common pattern gives authenticated users a higher ceiling than anonymous traffic. Open bootstrap/app.php and register your limiters inside the application bootstrap callback:
<?php
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
// Inside bootstrap/app.php
RateLimiter::for('api', function (Request $request) {
return $request->user()
? Limit::perMinute(120)->by($request->user()->id)
: Limit::perMinute(30)->by($request->ip());
});
RateLimiter::for('auth-actions', function (Request $request) {
return Limit::perMinute(5)->by($request->ip());
}); The by() method sets the partition key for each counter bucket. Use $request->user()->id for authenticated traffic. Omit it and every logged-in user shares one global bucket. I hit this during load testing on a legal-tech portal. One user's batch export locked out every other session within seconds.
For inline limits without a named limiter, pass arguments directly to the middleware: throttle:60,1 allows 60 requests per minute keyed by IP. Named limiters are cleaner when logic grows beyond a single number.
Attach limiters to API routes
Apply the named limiter in routes/api.php. Place authentication middleware before throttle when the limiter reads $request->user():
<?php
use Illuminate\Support\Facades\Route;
Route::middleware(['auth:sanctum', 'throttle:api'])->group(function () {
Route::get('/documents', [DocumentController::class, 'index']);
Route::post('/documents', [DocumentController::class, 'store']);
});
Route::middleware('throttle:auth-actions')->group(function () {
Route::post('/login', [AuthController::class, 'login']);
Route::post('/forgot-password', [AuthController::class, 'forgotPassword']);
}); Wrong middleware order is a silent bug. Throttle runs first, the user object is null, and every request lands in the guest bucket. See the official Laravel rate limiting documentation for additional limit types like perHour(), perDay(), and decay callbacks.
Why does Laravel API rate limiting need Redis in production?
The throttle middleware performs an atomic increment-and-check against your cache store. The file driver is neither atomic nor fast under concurrency. Two simultaneous requests can read the same counter, both pass, and your effective limit doubles. Under load, that race condition makes rate limiting unreliable.
Redis solves this with native atomic commands. INCR combined with EXPIRE runs as a single server-side operation. On projects like Nepal Gift Card and several legal portals, switching from file cache to Redis eliminated phantom 429 errors during traffic spikes. Limits now enforce precisely when they should. Pair this with Redis caching for Laravel so your infrastructure serves both throttling and application cache from one well-tuned instance.
Production Redis configuration
Set your cache driver to Redis in production:
# .env.production
CACHE_STORE=redis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
# Optional: separate DB index for rate limit keys
REDIS_CACHE_DB=1 Consider isolating rate limit keys from general application cache. Cache entries often carry longer TTLs. Under memory pressure, Redis may evict short-lived throttle keys first. That resets counters unexpectedly and lets abusers slip through. A dedicated Redis database index for throttling avoids this collision. For high-throughput APIs, read about custom rate limit keys in Laravel when multiple services share one Redis cluster.
How do you set tiered and endpoint-specific rate limits?
Static limits rarely match real usage. A free-tier consumer should not share the same ceiling as an enterprise partner. Laravel resolves limits dynamically at request time based on user attributes, API keys, or subscription tiers.
Tiered limits by subscription level
RateLimiter::for('partner-api', function (Request $request) {
if (!$request->user()) {
return Limit::perMinute(10)->by($request->ip());
}
$tier = $request->user()->api_tier;
return match ($tier) {
'enterprise' => Limit::perMinute(1000)->by($request->user()->id),
'pro' => Limit::perMinute(300)->by($request->user()->id),
default => Limit::perMinute(60)->by($request->user()->id),
};
}); Centralizing tier logic in one closure beats scattering conditionals across controllers. When pricing changes, you update a single file. For legal-tech platforms with different entitlements for firms versus individuals, this pattern keeps billing and throughput aligned. Our Mijar Law Associates client portal uses similar tiered access for document operations.
Segment limits by endpoint cost
Not every route costs the same. A cached listing tolerates higher throughput than a PDF export that spawns queue workers:
- Read-only listings: 200 requests per minute — cheap index lookups.
- Write operations: 60 per minute — protects database write capacity.
- Export and generation: 5 per minute — limits CPU and memory spikes.
- Authentication routes: 5 per minute per IP — slows brute-force attempts.
Define separate named limiters for each category. Attach the appropriate one per route group. This prevents one expensive call from consuming a user's entire quota on cheap reads. The concept parallels token bucket and sliding window strategies used in gateway-level throttling, but Laravel's built-in limiter covers most application needs without extra packages.
What HTTP headers and 429 responses should your API return?
Rate limiting only works when clients can react to it. HTTP 429 tells them to slow down. Standard headers tell them exactly how. Laravel's throttle middleware injects these automatically. Verify your reverse proxy or CDN forwards them unchanged.
Standard rate limit headers
| Header | Description | Example |
|---|---|---|
X-RateLimit-Limit | Maximum requests allowed in the window | 120 |
X-RateLimit-Remaining | Requests remaining in current window | 87 |
Retry-After | Seconds until window resets (sent with 429) | 34 |
Well-behaved clients read X-RateLimit-Remaining and throttle themselves before hitting the wall. On 429, Retry-After removes guesswork from backoff logic. The RFC 6585 specification defines 429 semantics that Laravel follows. Document these headers in your API reference. The guide on building REST APIs in Laravel the right way covers header conventions that pair well with throttling.
Custom JSON error bodies for 429
Mobile apps and third-party SDKs benefit from structured error payloads:
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(120)
->by($request->user()?->id ?: $request->ip())
->response(function (Request $request, array $headers) {
return response()->json([
'error' => 'rate_limit_exceeded',
'message' => 'Too many requests. Please slow down.',
'retry_after' => (int) ($headers['Retry-After'] ?? 60),
'limit' => (int) ($headers['X-RateLimit-Limit'] ?? 0),
], 429, $headers);
});
}); Include retry_after in the JSON body. Some HTTP clients strip headers on error responses. Duplicate the value so SDKs can parse it reliably. Use our JSON formatter tool to validate response payloads during development.
How do you test and monitor Laravel API rate limiting?
Rate limit configs fail silently. A typo in the limiter name, a missing Redis connection, or wrong middleware order leaves endpoints unprotected. Test explicitly. Monitor 429 rates in production.
Automated feature tests
public function test_api_respects_rate_limit(): void
{
$user = User::factory()->create();
for ($i = 0; $i < 120; $i++) {
$this->actingAs($user, 'sanctum')
->getJson('/api/documents')
->assertOk();
}
$this->actingAs($user, 'sanctum')
->getJson('/api/documents')
->assertStatus(429)
->assertHeader('Retry-After')
->assertJsonPath('error', 'rate_limit_exceeded');
} Use a dedicated Redis database in your test environment. Flush throttle keys in tearDown() to prevent flaky tests from leftover counters. For broader API security coverage, cross-reference the OWASP API Top 10 checklist and securing Laravel against OWASP risks.
Production monitoring
Track 429 response rates as an operational metric. A sudden spike signals an attack, a misconfigured client, or limits set too low for legitimate traffic. Log throttled requests with context:
- User ID or IP address that triggered the limit.
- Endpoint path and HTTP method.
- Configured limit and remaining count at time of block.
- User-Agent string to distinguish bots from real clients.
On legal-tech portals, I log auth throttling separately from general API throttling. Login spikes often mean credential stuffing. Document retrieval spikes may indicate scraping. Different events need different responses. For infrastructure teams in Nepal, cloud hosting options and pricing affects whether you run Redis on the same VPS or a managed instance with persistence enabled.
Combine application-level throttling with edge protection where needed. Laravel middleware patterns let you layer IP blocklists, request signing, and throttle policies in a predictable stack. For queue-heavy endpoints, coordinate limits with Redis queue worker scaling so rejected requests do not pile up in retry loops.
Key Takeaways
- Define named limiters in
bootstrap/app.phpand partition counters withby()— never share one bucket across all users. - Use Redis as your cache store in production; the file driver races under concurrent load.
- Place auth middleware before throttle when limits depend on the authenticated user.
- Segment limits by subscription tier and endpoint cost instead of applying one global cap.
- Return structured 429 JSON with
Retry-Afterin both headers and response body. - Write feature tests that hit the limit and monitor 429 rates as a production health signal.
People Also Ask
What is the default Laravel API rate limit?
Laravel's default API middleware group applies a limit of 60 requests per minute keyed by user ID or IP address. You override this by defining a custom named limiter and attaching it with throttle:your-limiter-name on route groups.
Can Laravel rate limiting work without Redis?
Yes, any cache driver works for development and low-traffic staging. Production APIs with concurrent requests need Redis or another atomic store. File and database drivers can race and double-count under parallel traffic.
How do you bypass rate limiting for internal services?
Check for a shared secret header or internal IP range inside your limiter closure and return Limit::none() for trusted callers. Keep the bypass list small and log every exempt request for audit purposes.
Does Cloudflare rate limiting replace Laravel throttling?
Cloudflare protects at the edge before traffic reaches your server. Laravel throttling protects application resources like database writes and queue dispatch. Use both layers — edge filtering for DDoS, application limits for business logic and per-user fairness.
Ship Rate Limiting Before Your Next Traffic Spike
Laravel API rate limiting is foundational infrastructure. Configure named limiters, back them with Redis, tier limits by user and endpoint, expose standard headers, and test the 429 path explicitly. Silent misconfigurations leave you exposed until a scraper or brute-force campaign proves it. Need an audit of your API security posture or tiered throttling for a production system? Explore API development services or contact us to discuss your project. For ongoing support after launch, reach out directly about maintenance and monitoring.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

