Rate Limiting
Rate limiting caps how many requests a client can make in a window. It protects your API from abuse (credential stuffing, scraping), ensures fair resource sharing, and keeps the service stable when one client misbehaves or a bug hammers an endpoint.
It's both a security control and a reliability control — the same mechanism that blocks a brute-force attack also stops a runaway script from taking down the service for everyone else.
TL;DR
- Limit by API key, user ID, or IP — pick the most specific identity you have.
- Choose an algorithm: token bucket for bursts, sliding window for smoothness.
- Return 429 Too Many Requests with a
Retry-AfterandRateLimit-*headers. - Use a shared store (Redis) so limits hold across multiple instances.
- Apply stricter limits to expensive or sensitive endpoints (login, search, exports).
Quick Example
A baseline IP limiter in Express that emits standard headers and a clean 429:
Core Concepts
Algorithms
What to key on
- Per user (authenticated) — the fairest unit.
- Per API key (service accounts) — supports tiering.
- Per IP (anonymous) — necessary but coarse (shared NATs, proxies).
- Per endpoint — tighten expensive operations independently.
How to respond
Return 429, a Retry-After (seconds), and RateLimit-Limit / RateLimit-Remaining / RateLimit-Reset so well-behaved clients can back off.
Best Practices
Use a shared store for distributed limits
In-memory counters break the moment you run more than one instance — each replica counts separately, so the real limit becomes instances × max. Use Redis (or an edge limiter) for a single source of truth. See Redis.
Tier by plan
Push limits to the edge when you can
A CDN/WAF (Cloudflare, AWS WAF) blocks floods before they reach your origin. Keep application-level limits too, for per-user fairness the edge can't see.
Common Mistakes
Counting in memory behind multiple instances
Rate limiting by IP behind a proxy
Returning 429 with no guidance
Always include Retry-After and RateLimit-* headers, so clients back off intelligently instead of retrying in a tight loop.
FAQ
Which algorithm should I use?
Token bucket is a great default — it allows short bursts while enforcing an average rate. Use a sliding window when you need smooth, boundary-free enforcement, and fixed window only when simplicity matters more than precision.
Where should counters live?
In a shared, fast store — Redis is the standard. In-memory only works for a single instance. For very high volume, enforce at the edge (CDN/WAF) and keep per-user limits in the app.
Should I limit by IP or by user?
Prefer the most specific identity available: API key or user ID for authenticated traffic, falling back to IP for anonymous requests. IP alone is coarse because many users share addresses behind NAT and proxies.
What status code and headers do I return?
429 Too Many Requests, plus Retry-After and the RateLimit-Limit/Remaining/Reset headers so clients know when to retry.
Related Topics
- API Security — Rate limiting as an abuse defense
- Redis — Distributed counter storage
- Caching — Reducing load that limits would otherwise reject
- REST API Design — Where
429fits in your status codes - Authentication — Throttling login endpoints