Rate limiting caps how many requests one visitor, key or IP address may send in a period of time. Requests over the cap are refused, usually with HTTP status 429 Too Many Requests and a Retry-After header saying when to try again. It protects login forms, contact forms and APIs from floods and runaway bills.
| Algorithm | How it counts | Good at | Weak spot |
|---|---|---|---|
| Fixed window | A counter per clock window, reset at each boundary | Simple, cheap to store | Double bursts at the window edge |
| Sliding window | Counts requests in the last N seconds from now | Smooth, fair limits | Stores more data per visitor |
| Token bucket | Tokens refill at a steady rate; each request spends one | Allows short bursts, then a steady pace | Two settings to tune |
| Leaky bucket | Requests drain out at a fixed rate; overflow is refused | Steady output to the server behind it | Bursts wait or get refused |
Updated
If your site has a sign-in page, a contact form, a checkout or an API that other apps call, someone will eventually hammer it, by accident or on purpose. Rate limiting is the rule that says how much is too much. Below you can press buttons to send requests and watch each algorithm accept or refuse them, then copy a working Nginx setup.
Each algorithm allows about 5 requests per 5 seconds (or a bucket of 5 moving at 1 per second). Send requests, let time pass, and watch what gets through.
No requests yet. Press a button above.
A teaching model with whole-second steps. Real limiters measure in milliseconds.
This allows each IP address 5 requests per second on the login path, with a burst of 10 served at once, and answers extra requests with 429 instead of the default 503.
The 10m zone stores the counters; the docs say one megabyte holds about 16 thousand 64-byte states.
http {
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/s;
server {
location /login {
limit_req zone=login burst=10 nodelay;
limit_req_status 429;
proxy_pass http://app;
}
}
}For a single server, a token bucket fits in a few lines. Keep counts in a shared store if you run more than one server.
Each key gets 10 tokens that refill at 1 per second. A refused call gets 429 and a Retry-After in whole seconds.
const CAPACITY = 10;
const REFILL_PER_SEC = 1;
const buckets = new Map();
function take(key) {
const now = Date.now() / 1000;
const b = buckets.get(key) ?? { tokens: CAPACITY, last: now };
const tokens = Math.min(CAPACITY, b.tokens + (now - b.last) * REFILL_PER_SEC);
if (tokens < 1) {
buckets.set(key, { tokens, last: now });
return { ok: false, retryAfter: Math.ceil((1 - tokens) / REFILL_PER_SEC) };
}
buckets.set(key, { tokens: tokens - 1, last: now });
return { ok: true };
}
// In a request handler:
// const r = take(req.ip);
// if (!r.ok) { res.setHeader("Retry-After", String(r.retryAfter)); res.status(429).end(); return; }Status 429 with a Retry-After of 30 seconds.
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 30
{"error":"rate_limited","message":"Too many requests. Try again in 30 seconds."}
At the edge, meaning your CDN, load balancer or web server, a refused request costs almost nothing because your app never runs. That is the right place for simple per-IP rules.
Inside the app you know who the caller is, so you can give paying customers a higher allowance or count by API key. Many sites use both layers: a generous edge rule and a stricter per-account rule.
Retry-After takes one of two forms: a number of seconds, such as 120, or an HTTP date after which to try again. The same header is also sent with 503 during planned downtime.
A polite client waits at least that long. If the header is missing, back off with growing gaps between attempts rather than retrying instantly.
RFC 6585 defines 429 Too Many Requests as the status for a client that has sent too many requests in a given amount of time. The response may carry a Retry-After header telling the client how long to wait.
By default the limit_req module answers refused requests with 503. You can switch that to 429 with the limit_req_status directive, which most APIs prefer because it tells the caller exactly what happened.
Not yet. The RateLimit and RateLimit-Policy fields come from an IETF working group Internet-Draft that was still in progress as of October 2026, so some services send older X-RateLimit headers instead. Treat them as helpful hints.
A token bucket lets a caller spend saved-up tokens in a burst and then settles to the refill rate. A leaky bucket smooths traffic into a fixed outflow. Nginx documents its limit_req module as using the leaky bucket method.
No. It handles abuse from a few sources well, but traffic spread across thousands of addresses needs protection at your host or CDN as well. Rate limiting is one layer, not the whole defence.
Related: HTTP 429 Too Many Requests, Debounce vs throttle, Nginx load balancing, What is an API key?, GitHub webhooks.
Start for free or see all free tools. We.Inc is free to start, with no card needed. AI building is on paid plans from $20 a month.
We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.