
December 07, 2025
13 min read
By Kokil Thapa | Last reviewed: September 2026
Your login endpoint just took 40,000 POST requests in three minutes from a rotating IP pool, your MySQL connection queue is full, and paying customers see 504 errors. That is what happens when a public API ships without rate limiting and abuse prevention. APIs sit at the centre of mobile apps, SaaS dashboards, payment callbacks, and partner integrations—every exposed route is a cost centre someone will probe. This practical guide to API rate limiting and abuse prevention in modern web apps walks through the algorithms, Laravel 13 implementation patterns, Redis-backed counters, response standards, and layered defences I use on production systems. If you build or maintain APIs, the patterns here apply whether you run on a single Ubuntu VPS in Nepal or a multi-region cloud stack.
What Is API Rate Limiting and Abuse Prevention in Modern Web Apps?
Rate limiting controls how many requests a client may send. Abuse prevention controls what happens when traffic looks malicious—credential stuffing, scraping, tenant resource hogging, or repeated calls to expensive endpoints like PDF generation or LLM inference.
On real client projects—booking portals, eCommerce carts, legal-tech intake forms—I treat both as infrastructure requirements, not optional middleware. A single abusive client can exhaust PHP-FPM workers, Redis memory, and database connections faster than horizontal scaling can recover. Fair usage across tenants matters especially in multi-tenant SaaS applications built with Laravel, where one noisy neighbour can degrade service for everyone else on the same cluster.
Common abuse patterns every public API eventually faces:
- Brute-force and credential stuffing — automated login and token-guessing against auth endpoints
- Scraping and data extraction — systematic harvesting of product catalogues, pricing, or directory listings
- Traffic bursts — DDoS-like spikes that saturate CPU, memory, or upstream bandwidth
- Resource exhaustion — repeated calls to report builders, image processors, or AI endpoints
- Rate-limit evasion — IP rotation, disposable API keys, or distributed botnets
How Do Rate Limiting Algorithms Compare for Production APIs?
Four algorithms power most production rate limiters. Pick based on accuracy, memory cost, and whether you need controlled bursts. For a deeper algorithm walkthrough, see our companion piece on token bucket and sliding window rate limiting.
| Algorithm | Memory | Burst handling | Boundary burst risk | Best for |
|---|---|---|---|---|
| Fixed window counter | Very low | Poor | High — double traffic at window edges | Internal tools, coarse limits |
| Sliding window log | High | Excellent | None | Strict per-second accuracy |
| Sliding window counter | Low | Good | Low | CDN and gateway defaults |
| Token bucket | Medium | Excellent — allows bursts | None | Public APIs, Laravel throttle |
Fixed window counter
Count requests in fixed windows (e.g., per minute). Reset when the window rolls over.
// Fixed window: 100 requests per minute
Window: 14:00:00 - 14:00:59 → Count: 87 (allowed)
Window: 14:01:00 - 14:01:59 → Count: 0 (reset)
Simple and cheap, but a client can send 100 requests at 14:00:59 and 100 more at 14:01:00 — effectively 200 requests in two seconds.
Sliding window log
Store every request timestamp. Count how many fall inside the trailing window. Accurate, but memory grows with traffic volume — impractical for high-throughput public APIs without sampling.
Sliding window counter
Hybrid approach used by Cloudflare and AWS API Gateway: weight the previous window's count against the current one. Good accuracy with O(1) memory per client key.
Token bucket
Each client holds a bucket of tokens that refill at a steady rate. Each request consumes one token; empty bucket means rejection.
// Token bucket: 10 tokens, refill 1 token per second
Request at T=0: 10 tokens → 9 tokens (allowed)
Request at T=0.1: 9 tokens → 8 tokens (allowed)
Request at T=1: 1 token + 1 refilled → 1 token (allowed)
Laravel's built-in throttle middleware uses a token-bucket-style implementation backed by your cache store. The official Laravel rate limiting documentation covers named limiters and custom responses.
How Do You Implement API Rate Limiting in Laravel 13?
Laravel 13 ships with the ThrottleRequests middleware and named rate limiters via the RateLimiter facade. Configure your cache driver to Redis 8.10 in production — file or database drivers break under concurrent load. For broader API design context, read our Laravel API best practices guide and the dedicated article on rate limiting and API throttling in Laravel.
Basic route throttling
// routes/api.php — 60 requests per minute per IP
Route::middleware('throttle:60,1')->group(function () {
Route::get('/products', [ProductController::class, 'index']);
Route::get('/products/{id}', [ProductController::class, 'show']);
});
Named rate limiters
Define limiters in bootstrap/app.php or a service provider, then reference them by name in routes or middleware groups.
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});
RateLimiter::for('login', function (Request $request) {
return Limit::perMinute(5)->by($request->ip());
});
RateLimiter::for('heavy-operations', function (Request $request) {
return Limit::perMinute(10)->by($request->user()->id);
});
Per-user vs per-IP keys
RateLimiter::for('user-api', function (Request $request) {
return $request->user()
? Limit::perMinute(100)->by($request->user()->id)
: Limit::perMinute(20)->by($request->ip());
});
Authenticated API consumers should be keyed by user ID or API key hash—not IP—because mobile users and corporate NATs share addresses. Unauthenticated public endpoints stay IP-keyed with stricter ceilings.
Tiered limits by subscription plan
RateLimiter::for('tiered', function (Request $request) {
$user = $request->user();
return match ($user?->plan) {
'enterprise' => Limit::perMinute(1000)->by($user->id),
'business' => Limit::perMinute(300)->by($user->id),
'starter' => Limit::perMinute(60)->by($user->id),
default => Limit::perMinute(20)->by($request->ip()),
};
});
On the Nepal Gift Card platform and similar Laravel eCommerce APIs I have maintained, tiered limits map directly to commercial plans—enterprise partners get higher ceilings without giving every anonymous visitor the same quota.
Custom throttle keys for sensitive endpoints
Login and password-reset routes benefit from composite keys. Combine IP with email or username so one attacker cannot exhaust the shared IP budget for an entire office network, while still blocking targeted credential stuffing against a single account. See Laravel rate limiting with custom keys for advanced patterns.
How Should APIs Respond When Rate Limits Are Exceeded?
Return HTTP 429 with machine-readable headers and a JSON body that tells clients exactly when to retry. Laravel adds X-RateLimit-* headers automatically; align with the emerging IETF standard documented in the RateLimit header draft where possible.
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1714500120
Retry-After: 45
{
"type": "https://example.com/errors/rate-limit",
"title": "Too Many Requests",
"status": 429,
"detail": "Rate limit exceeded. Retry after 45 seconds.",
"retry_after": 45
}
Structure error bodies using RFC 7807 Problem Details so SDK consumers parse failures consistently. Always include Retry-After—well-behaved clients honour it and stop hammering your origin.
Test your 429 payloads with the JSON formatter during development to catch malformed response bodies before they reach mobile app stores.
How Do You Layer Edge, Gateway, and Application Rate Limits?
Single-layer throttling fails when attackers rotate IPs or when volumetric floods saturate network capacity before Laravel ever runs. Stack defences so each layer catches what the previous one missed.
- Edge protection (CDN / WAF) — block known bad ASNs, apply bot scores, and enforce coarse per-IP limits before traffic hits your origin. Pair with guidance from securing your website and server in Nepal for UFW, fail2ban, and TLS hardening on the origin itself.
- API gateway — AWS API Gateway usage plans, Kong, or Traefik enforce per-key quotas independently of application code. Useful with serverless Laravel on AWS Lambda and Vapor where the gateway sits in front of Lambda handlers. Compare gateway options in our Kong API gateway guide.
- Application middleware — Laravel throttles with business logic: plan tiers, endpoint sensitivity, authenticated vs anonymous paths.
- Endpoint-specific rules — stricter limits on auth, uploads, and expensive operations.
- Bot detection — reCAPTCHA v3 or Cloudflare Bot Management on public forms and search endpoints.
Endpoint-specific limit recommendations
- Login — 5 attempts per minute per IP, exponential backoff after repeated failures
- Password reset — 3 requests per hour per email address
- File upload — 10 uploads per hour per authenticated user
- Report generation — 5 requests per hour (CPU-bound work)
- Search — higher read limits with Redis result caching to protect the database
- Webhooks outbound — queue and throttle delivery; see webhook design patterns for reliability
Cross-check your overall posture against the OWASP API Security Top 10, especially API4:2023 Unrestricted Resource Consumption and API6:2023 Unrestricted Access to Sensitive Business Flows. Our OWASP API Top 10 checklist maps each risk to Laravel-specific mitigations.
How Do You Use Redis for Scalable Rate Limiting?
Laravel's rate limiter stores counters in your configured cache driver. For production APIs, point CACHE_STORE at Redis 8.10 — atomic INCR and TTL expiry prevent race conditions when hundreds of PHP-FPM workers hit the same key simultaneously. Read Redis caching patterns for web apps for broader cache architecture beyond throttling.
# .env — production API stack
CACHE_STORE=redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
Why Redis wins for rate counters:
- Atomic increments — no lost counts under concurrency
- Sub-millisecond latency — throttle checks add negligible overhead per request
- TTL expiry — keys vanish automatically when windows close
- Horizontal scaling — Redis Cluster handles high-traffic APIs across multiple app servers
Custom Redis counter for edge cases
When Laravel's built-in limiter is not enough—per-endpoint composite keys, sliding windows, or cross-service quotas—drop to raw Redis:
$key = "rate_limit:{$userId}:{$endpoint}";
$limit = 100;
$window = 60;
$current = Redis::incr($key);
if ($current === 1) {
Redis::expire($key, $window);
}
if ($current > $limit) {
abort(429, 'Rate limit exceeded');
}
On multi-server Laravel deployments, never use file-based cache for counters—each server maintains separate counts and limits become meaningless. Session configuration for load-balanced setups is covered in our notes on Laravel session configuration for multi-server environments.
How Do You Monitor API Abuse and Rate Limit Events?
Rate limiting you cannot see is rate limiting you cannot tune. Log every 429 with client key, endpoint, user agent, and geographic data. Alert on patterns that suggest attack—not normal usage spikes.
- Elevated 429 rates — sudden jumps may indicate scraping or a misconfigured client retry loop
- Per-endpoint hotspots — login or search routes targeted disproportionately
- IP rotation patterns — many IPs, identical user agents, same API paths
- Limit bypass attempts — clients cycling API keys or JWT tokens
- Latency correlation — 429 storms that precede database slow-query alerts
Ship structured logs to Grafana, CloudWatch, or your existing stack. Pair rate-limit metrics with load tests using k6 load testing for PHP apps before launch so you know your ceilings before attackers find them.
Authentication architecture matters too—Sanctum token issuance should itself be throttled. Our guide on building a REST API with Laravel Sanctum covers token lifecycle alongside access control.
For production APIs that need abuse-resistant design from the ground up—payment callbacks, partner integrations, mobile backends—review our API development services in Nepal or explore how we ship secure integrations on projects like Adventure Third Pole Trek. Further background on Kokil's work is on the about me page, and ongoing hardening fits under testing and optimization services.
Key Takeaways
- Stack rate limiting at edge, gateway, and Laravel application layers—one layer alone will not stop determined abuse.
- Use token bucket or sliding window counters in production; avoid naive fixed windows on public APIs due to boundary burst risk.
- Key limits by authenticated user ID or API key hash; reserve IP-based keys for anonymous endpoints with lower ceilings.
- Return HTTP 429 with
Retry-After, rate-limit headers, and RFC 7807 JSON bodies so clients back off correctly. - Back Laravel 13 throttles with Redis 8.10—never file cache on multi-server deployments.
- Monitor 429 rates and endpoint hotspots; alert on attack patterns, not every legitimate limit hit.
People Also Ask
What is the difference between rate limiting and throttling?
Rate limiting caps total requests within a time window and typically rejects excess calls with HTTP 429. Throttling slows request processing—queuing or delaying responses—rather than rejecting them outright. Most REST APIs use hard rate limits; throttling appears more often in queue-based systems and upstream service protection.
How many API requests per minute is normal?
There is no universal number. Public read endpoints often allow 60–300 requests per minute per key; login routes sit at 5–10 per minute per IP; expensive write or report endpoints may allow only a handful per hour. Set limits from load-test data and actual client behaviour, not arbitrary round numbers.
Does rate limiting stop DDoS attacks?
Application-level rate limiting mitigates application-layer floods and abusive clients but cannot absorb volumetric network DDoS alone. Combine Laravel throttles with CDN or WAF edge protection that absorbs traffic before it reaches your origin servers.
Can rate limiting block legitimate users?
Yes—shared NAT IPs, aggressive mobile app retry logic, and bulk admin imports can trigger limits. Mitigate with user-keyed limits for authenticated traffic, higher ceilings for verified partners, and clear 429 responses with accurate Retry-After values so clients wait instead of retrying immediately.
Ship APIs That Survive Real-World Traffic
API rate limiting and abuse prevention in modern web apps is not a single middleware line—it is a design decision spanning algorithms, response contracts, Redis infrastructure, layered edge defences, and observability. Start with Laravel named limiters on your hottest routes, move counters to Redis, standardise 429 responses, and add gateway or CDN rules before your next traffic spike proves the gaps.
Need help architecting rate limits for a production API, payment webhook surface, or multi-tenant SaaS backend? Contact us to discuss scope, or browse the portfolio for examples of Laravel APIs shipped under real load.
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.

