Rate Limiting in Laravel 13: Protect Your API with Throttle and RateLimiter
In Laravel 13, rate limiting is configured in minutes with the throttle middleware or RateLimiter: when you exceed the limit, your API answers 429 Too Many Requests with the Retry-After header. Without it, bots, brute force, and traffic spikes leave your API exposed.
What Is Rate Limiting and Why Your API Needs It
Rate limiting restricts how many requests a client can make within a time window. It is the first line of defense for any API: without it, a bot can crawl your routes unchecked, a poorly written client can saturate an endpoint, and an attacker can try passwords on your login form until one works.
The Problem: Bots, Brute Force, and Traffic Spikes
The three scenarios share a symptom: requests far above normal usage. Bots crawl and call the API nonstop, brute force tries credentials in a loop, and a legitimate traffic spike can raise the cost of an endpoint that processes heavy data. Rate limiting puts a predictable ceiling on each of those cases.
How Laravel Responds: the 429 Too Many Requests Error
When a client exceeds the limit, Laravel responds with HTTP 429 Too Many Requests and includes the Retry-After header to indicate when the client can retry. It is the standard signal for the client to wait before calling again, and the response can be customized so the message explains the limit in your own format.
The Throttle Middleware: First Steps
The most direct way to apply rate limiting in Laravel 13 is the throttle middleware on a route or a group of routes. It requires nothing else: you pick the limit and the window, and the framework handles the rest.
Segments: throttle:60,1 Means 60 Requests per Minute
The classic syntax is throttle:60,1: the first segment is the maximum number of requests and the second is the window in minutes. A search endpoint that consumes many resources can use throttle:30,1, while a public content route can handle throttle:300,1. That is how you match the limit to the real cost of each operation.
The Default api Limiter in Laravel
Routes in routes/api.php use the api limiter by default, configured in the application bootstrap to allow 60 requests per minute per user or IP. That limiter is applied with throttle:api and can be overridden by defining your own version with RateLimiter::for('api', ...), as you will see next.
Applying Throttle to Route Groups in routes/api.php
To protect a whole group, apply the middleware in the Route::middleware of the group. A Laravel 13 API with Sanctum usually has an auth:sanctum group with throttle:api, so authenticated tokens are also limited. If your application uses access tokens, the guide on Laravel 13 and Sanctum explains how to build that authentication layer.
Named Limiters with RateLimiter::for()
When the default limit is not enough, Laravel lets you register named limiters in the boot method of a service provider. A named limiter centralizes the configuration and reuses it across several routes by just citing its name.
Registering Limiters in AppServiceProvider
In AppServiceProvider::boot() you define RateLimiter::for('api', fn ($request) => Limit::perMinute(60)->by(...)). The closure receives the request and returns the real limit for that client, which allows deciding on each call how many requests are allowed.
Limit Objects: perMinute, perHour, perDay, and by()
The Limit object offers ready-to-use windows: perMinute, perHour, and perDay build the limit with a single call. The by() method defines the counter key, meaning what identifies each client: an authenticated user, an IP, or a combination of both. Changing that key changes who shares the limit.
Referencing the Limiter by Name in Middleware
Once registered, the limiter is applied with throttle:api or whichever name you chose. The middleware evaluates the closure on every request, so the limit can vary by context without touching the routes.
Dynamic Limits: by User, IP, or Plan
The power of the system appears when the limit depends on who is calling. The same endpoint can allow few requests to an anonymous visitor and many more to a paying user.
by() to Tell Authenticated Users Apart from Visitors
The closure can resolve the counter key with by(fn ($request) => $request->user()?->id ?: $request->ip()). That way, an authenticated user is counted by their ID and a visitor by their IP, preventing multiple users behind the same IP from sharing a limit meant for one.
Plan-Based Limits (SaaS): a Higher Limit for Paying Users
In a SaaS application, the closure can check the user's plan and return a different Limit: 60 requests per minute for free accounts and 600 for Enterprise plans. The same middleware protects the endpoint and each plan gets the treatment it paid for.
Customizing the 429 Response with response()
The Limit object accepts response() to return your own JSON when the limit is exceeded, with whatever message you want to show the client. That is how the 429 explains the limit in your API's language instead of the generic text.
Protecting Login Against Brute Force
Rate limiting shines in authentication: a login form without limits allows trying passwords forever. With a tight limiter, failed attempts are cut off in seconds.
The 5 Attempts per Minute per Email and IP Pattern
The typical pattern defines an auth limiter of 5 requests per minute and applies it to the login and registration routes. The counter key combines email and IP with by(), so an attacker rotating IPs cannot dodge the limit by always using the same address.
Combining Throttle with Validation and Account Lockout
Throttle does not replace validation or account lockout: it complements them. Validation rejects invalid input, rate limiting cuts off looping attempts, and a temporary account lockout punishes persistent patterns. The three layers together turn a vulnerable login into a defended one.
Counter Storage: Cache and Redis
The rate limiter needs a place to store the counters, and that place decides whether the limit works on one server or several.
The Rate Limiter Uses the Default Cache
By default, the counter is stored in the cache driver configured in config/cache.php. For an application on a single server, the local cache is enough and requires no extra configuration.
Redis for Shared Limits Across Servers
If your API runs on several servers behind a load balancer, each server's local cache does not share state with the others: each one would count its own requests and the global limit would multiply. Using Redis as the counter store makes the limit shared and consistent across the whole infrastructure.
Best Practices
Rate limiting is designed per endpoint, not copy-pasted. Well-chosen limits protect without breaking the user experience.
Different Limits per Endpoint: Don't Apply One Size to All
Each route has a different cost and usage: a heavy search tolerates 30 requests per minute, a light query can handle 300, and an internal webhook may need more. Defining limits per endpoint avoids two mistakes: saturating the expensive one and blocking the legitimate one.
The X-RateLimit-Limit and X-RateLimit-Remaining Headers
Informing the client about its state avoids surprises. The X-RateLimit-Limit and X-RateLimit-Remaining headers can be added in the custom response, so the API consumer knows how much is left before hitting the 429.
Don't Aggressively Limit Public Content Routes
Routes that serve public content, like the homepage or blog pages, receive legitimate traffic from many visitors behind shared IPs. Applying aggressive limits to them blocks real users, so leave them with generous limits and focus protection on sensitive endpoints, like login and write operations.
Conclusion
Rate limiting in Laravel 13 is built-in, configurable, and adapts to the real case: the throttle middleware solves most cases, RateLimiter adds per-user, per-IP, or per-plan limits, and Redis makes it consistent across servers. Protect your API today: define limits per endpoint, shield login against brute force, and let the 429 do its job. If you want to go deeper into the security layer, also check the tutorial on custom middleware in Laravel 13 and keep reading the blog for more web development guides.