Building a rate limiter properly is real work. Not a toy — a real one, with atomic counters and a sliding window and a 429 Too Many Requests that actually respects Retry-After. You learn to keep the counters off a per-machine cache, because per-machine counters don’t compose, and to put them on strongly consistent storage. You tune the thresholds against real traffic. When it’s done, it works.
And it stops almost none of the attacks people install rate limiting to stop.
That’s an uncomfortable sentence, because the whole industry treats “add rate limiting” as a security control — bullet point on the hardening checklist, right under “enable MFA” and “rotate secrets.” It is not one. Rate limiting is an availability and cost control that happens to inconvenience the dumbest possible attacker. We filed it in the wrong drawer, and the filing error has consequences.
What rate limiting actually defends
Here’s the thing it’s genuinely good at: keeping one loud client from ruining everyone’s day.
A script hammering your login endpoint 500 times a second from a single IP will exhaust connections, blow up your database, and run up your bill. Rate limiting shuts that off cleanly. A scraper trying to pull your entire catalog in an afternoon — throttled. A buggy integration stuck in a retry loop — contained. A single machine trying every password against one account — yes, that too.
All of these share a property: one source, high volume. That’s the entire threat model rate limiting addresses. It’s a valve. Valves are useful. Build one.
But notice what every one of those examples has in common, because the attackers noticed it years ago.
The attack it doesn’t touch
Credential stuffing is the attack that actually empties accounts, and it was designed — deliberately, as an engineering response — to route around rate limits.
The economics are brutal. Someone breaches Site A, dumps a hundred million email/password pairs, and those pairs get replayed against Site B, C, and yours, because people reuse passwords. The attacker doesn’t need to guess. They already have credentials that work somewhere; they’re just checking which ones also work on you. And they’ve read the same hardening checklist you have.
So they don’t send high volume from one source. They send low volume from everywhere. Okta, for one, documented a wave of credential stuffing in April 2024 that routed millions of requests through residential proxy services — real home broadband and mobile connections, rented by the gigabyte — so that each individual IP makes only a handful of attempts before the traffic rotates away. Ten requests here. Six there. Each address, on its own, looks exactly like a person who fat-fingered their password twice and got it right on the third try.
Your per-IP rate limiter sees nothing. There is nothing to see. Every source is under the threshold because the whole architecture of the attack is staying under your threshold. You built a valve for a firehose, and they showed up with thousands of drinking straws.
The part nobody mentions: you’re teaching them
This is the detail that rarely makes the checklist.
A rate limit isn’t silent. It talks back. When a client crosses the line, you tell them — 429, sometimes with a helpful Retry-After: 60 so well-behaved clients know when to come back. That header is a courtesy to legitimate integrations. It is also a free oracle for an attacker.
A serious credential-stuffing operator runs your login a few times and watches for the exact moment you start returning 429s. Now they know your threshold. Now they tune their per-IP rate to sit one request underneath it, permanently. You didn’t block them. You handed them the calibration data to become invisible, and you did it in a standardized header the tooling already parses.
The naive brute-forcer gets stopped. The competent one gets informed. And the competent one is the one draining accounts.
Why the mislabel matters
If this were just pedantry — “actually, that’s an availability control” — I wouldn’t bother writing it down. The reason it matters is that calling rate limiting a security strategy makes teams stop too early.
The typical version goes like this. Someone asks “how are we protecting the login endpoint?” and the answer comes back: “we’ve got rate limiting.” Box checked. Meeting over. Except the login endpoint is now defended against exactly one adversary — the amateur — and wide open to the professional whose entire business model is account takeover at scale. The checklist made everyone feel finished at the precise moment the real work should have started.
The real work is harder and less satisfying to put on a slide. Detecting credential stuffing means correlating across the fleet, not per-IP: velocity of failures by ASN, sudden spikes in login attempts against dormant accounts, impossible-travel patterns, device fingerprints that don’t match history, password-spray shapes where one password is tried against thousands of usernames. It means treating a burst of individually-innocent requests as a single coordinated event, which per-IP counting cannot do by construction. And the thing that actually breaks credential stuffing isn’t detection at all — it’s making the stolen passwords worthless, which is what MFA and passkeys are for. That’s the control that belongs in the drawer everyone files rate limiting in.
Keep the valve
None of this means rip out your rate limiter. Availability is real. Cost is real. The amateur with a single script is real, and cheap to stop, and you should stop him.
Just call it what it is. Rate limiting protects your infrastructure from getting overwhelmed. It does not protect your users’ accounts from getting stolen, and the attackers who steal accounts have known that longer than most of the people deploying it. Put it on the reliability checklist, where it earns its place. Then go do the security work you told yourself was already done.
A good rate limiter is a good valve. It’s just not a lock, and it’s time to stop pretending the two are the same thing.