I spent a good chunk of last year building a rate limiter. Not a toy — a real one, with atomic counters and a sliding window and a 429 Too Many Requests that actually respected Retry-After. I moved it off the cache layer because per-machine counters don’t compose, put it on strongly consistent storage, tuned the thresholds against real traffic. It works. I’m proud of it.
And it stops almost none of the attacks people install rate limiting to stop.
That sentence took me a while to say out loud, 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. I’d build mine again tomorrow.
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. Documented campaigns have spread a single attack across hundreds of thousands of residential IP addresses — real home broadband and mobile connections, rented by the gigabyte through proxy services — with each individual IP making only a handful of attempts before rotating 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 two hundred thousand drinking straws.
The part nobody mentions: you’re teaching them
This is the detail that changed how I think about it.
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.
I’ve watched it happen. 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.
I built a good valve last year. It’s just not a lock, and I’ve stopped pretending the two are the same thing.