A rate limit sounds simple: allow only a certain number of requests during a period. The difficult security question is not the number. It is what you count together.
Suppose a login endpoint allows five failed attempts per minute from each IP address. That can slow one client, but an attacker using many addresses can still make many guesses against the same account. Change the rule to five failures per account and another problem appears: anyone who knows a username may be able to keep that user’s account throttled.
Both rules are rate limits. They defend different boundaries and have different failure modes.
This article develops a mental model for security-focused rate limiting: identify the action being abused, choose identities that represent the scarce security opportunity, and combine limits when one identity is too easy to change or weaponize. The goal is not to find a universal threshold. It is to make each allowed attempt consume the right budget.
A rate limit is a budget for repeated actions
Think of a rate limiter as a budget attached to an identity.
request
|
v
choose limit key
|
v
consume budget ---- budget available ----> continue
|
+----------- exhausted -------------> delay, reject, or challengeThe limit key is the value used to decide which requests share a budget. It might represent a network address, account, authenticated user, API credential, session, device signal, or a combination of several values.
This choice determines the security meaning of the control.
A per-IP limit says, roughly, “this network source may perform this action only this quickly.” A per-account limit says, “this account may receive only this many attempts.” A per-API-key limit says, “this authenticated client may consume only this much capacity.”
The counter algorithm matters, but choosing the wrong identity can make a perfectly implemented counter ineffective.
Start with the action an attacker wants to repeat
Rate limiting is useful when repetition increases an attacker’s chance of success or consumes a scarce resource. Examples include password guesses, one-time-code guesses, password-reset requests, expensive report generation, and API operations with meaningful backend cost.
Before choosing a threshold, write down four things:
- the repeated action;
- what the attacker gains from repetition;
- which identity or resource the action targets;
- which properties the attacker can cheaply change.
Consider password authentication. The security problem is not simply “too many HTTP requests.” It is repeated guesses against authenticators.
If the attacker can distribute requests across many source addresses, an IP-only budget does not tightly bound guesses against one account. If an account-only budget causes a long hard lockout, the attacker may deliberately exhaust another person’s budget to deny access.
The useful design question becomes:
Which budgets together make abusive repetition expensive without giving strangers an easy way to block legitimate users?
That framing is more durable than copying a requests-per-minute value from another application.
Use multiple identities when the threat crosses one boundary
For a login flow, a practical design may maintain more than one budget:
failed login
|
+--> source budget
|
+--> target-account budget
|
+--> broader abuse signalsThe source budget reduces the rate at which one origin can try many accounts. The account budget reduces the rate at which many origins can concentrate guesses on one account. Neither is a complete defense by itself.
This is an example of defense in depth: independent controls cover different ways the same abuse can be distributed.
Do not assume an IP address equals a person. Many legitimate users can share one public address, and one attacker can use many addresses. IP-based limits are still useful as a coarse signal, especially before authentication, but their limitations should shape the response and threshold.
After authentication, a stable account or API-client identity is often a more meaningful key for operations performed by that principal. Network-level budgets can remain as an additional layer rather than becoming the only identity.
Prefer slowing abuse over easy permanent lockouts
A security control can itself become an attack surface. A strict rule such as “lock the account for one hour after five failures” lets anyone who can trigger those failures potentially deny service to the account.
For many systems, progressive throttling is a better starting point. As failures accumulate, the service can make subsequent attempts arrive more slowly, require additional verification, or temporarily reject attempts. Successful legitimate authentication and carefully designed recovery paths can influence how the budget is restored, depending on the application’s threat model.
The important property is that repeated guessing becomes increasingly costly while recovery remains possible for the legitimate user.
Authentication standards and application-security guidance commonly treat throttling as protection against online guessing, while also recognizing that aggressive lockout can create denial-of-service risk. That trade-off is why a threshold should be chosen from the authenticator strength, application sensitivity, expected user behavior, and recovery design rather than treated as a universal constant.
Count the event that represents risk
A common mistake is to increment a counter at the easiest place in the request path rather than at the event that matters.
For authentication, you may need to distinguish malformed requests from genuine failed authenticator checks. For an expensive export endpoint, the scarce event may be starting a backend job rather than merely receiving an HTTP request. For password reset, both request volume and messages sent to a particular destination may matter.
This does not mean suspicious requests should be free. It means counters should represent explicit security budgets.
For example:
HTTP request
|
validate basic shape
|
check relevant budgets
|
perform protected operation
|
record outcome and update applicable budgetsProduction ordering depends on the endpoint. A request may need an inexpensive coarse limit before parsing so that malformed traffic cannot consume unlimited resources. A more specific application limit can then protect the sensitive operation after enough identity is known.
The principle is to place cheap controls before expensive work while preserving the identity information needed for meaningful security decisions.
Choose an algorithm that matches acceptable bursts
Once the security identity is correct, the counting algorithm determines how traffic is distributed over time.
A fixed window such as “100 requests each clock minute” is simple, but requests near a window boundary can create a larger short burst: one budget can be consumed just before the boundary and another immediately after it.
A sliding-window design measures activity over a moving interval and gives a closer bound on requests in any such interval, usually at greater implementation cost.
A token bucket represents capacity as tokens that replenish over time. A request consumes a token. This naturally allows a controlled burst while limiting the long-term rate.
There is no universally correct algorithm. If a short legitimate burst is expected, a token bucket can model that requirement clearly. If the security property requires a tighter bound on attempts during any interval, a sliding approach may fit better. The decision should follow the threat model rather than algorithm popularity.
Make distributed enforcement consistent enough
A rate limiter running on one application process is straightforward. A service running on many instances introduces concurrency.
If each of ten instances independently allows ten attempts, a client that reaches all instances may effectively receive far more than the intended shared budget. A centralized or otherwise coordinated counter can reduce this inconsistency when the limit is meant to apply across the service.
Counter updates also need suitable atomicity. Two simultaneous requests should not both observe the last available unit and both consume it when the intended invariant allows only one. Use storage operations or rate-limiting primitives whose concurrency guarantees match the budget you are enforcing.
Not every limit needs perfect global precision. A coarse capacity-control limit may tolerate small overshoot. A limit protecting a low-entropy authentication value may require tighter enforcement. State that assumption explicitly instead of assuming every distributed counter has identical semantics.
Decide what happens when the limiter fails
The limiter itself can become unavailable. Whether to allow or reject requests during that failure is a security and availability decision.
For a low-risk public endpoint, temporarily allowing traffic may be preferable to taking the service offline. For a sensitive operation whose safety depends heavily on bounded attempts, silently bypassing the limiter may remove an important part of the threat model.
A useful design records this decision per protected operation:
limiter unavailable
|
+--> low-risk operation: degraded capacity control may be acceptable
|
+--> high-risk bounded-attempt operation: reject or use a fallback controlThere is no single fail-open or fail-closed rule for every rate limiter. Consider the consequence of unlimited attempts, the consequence of denying legitimate requests, and whether an independent fallback exists.
Monitor limiter failures separately from ordinary limit exhaustion. Otherwise an outage can look like normal traffic behavior.
Avoid leaking more information through throttling
Authentication and recovery flows can accidentally reveal account state through different messages or visibly different behavior. A response such as “this account is rate limited” may confirm that an identifier maps to an account when the rest of the flow intentionally avoids that disclosure.
Keep externally visible behavior consistent with the enumeration threat model of the endpoint. Internally, retain enough detail for operations and abuse investigation without exposing unnecessary distinctions to an unauthenticated requester.
This does not require every response to take exactly the same amount of time. It means the rate-limit design should not casually undo privacy properties established elsewhere in the authentication or recovery flow.
Verify the control as an attacker would distribute requests
Testing one client repeatedly against one endpoint proves only the simplest path.
Verify the boundaries you intended to enforce. For a login flow, test repeated failures from one source against one account, one source against many accounts, and multiple simulated sources against one account. Confirm that the corresponding budgets behave independently and that legitimate recovery remains possible.
For authenticated APIs, verify that one client’s exhaustion does not incorrectly consume another client’s budget. For shared network limits, test the expected behavior of multiple legitimate users behind the same source address.
Also test concurrency and limiter-storage failure. A security control that works only during serial requests on one application instance has not demonstrated the properties of a distributed production deployment.
Metrics should make the same boundaries observable: allowed operations, throttled operations, limiter errors, and saturation grouped by useful security identities without placing passwords, tokens, or other secrets into logs.
Know what rate limiting does not solve
Rate limiting reduces risk from repeated actions under the identities and budgets you can enforce. It does not prove that a request is legitimate.
A sufficiently distributed attacker may stay below coarse source limits. Stolen valid credentials can succeed on the first attempt. An authorized client can abuse an operation within its quota. Large volumetric denial-of-service attacks may need upstream network or edge controls before traffic reaches the application.
Complementary controls depend on the threat. Authentication can use strong password handling and multi-factor authentication. Sensitive workflows may need authorization and transaction-specific abuse checks. Resource-intensive endpoints may need queues, bounded workloads, timeouts, or capacity isolation in addition to request-rate controls.
Rate limiting is strongest when it bounds a clearly defined opportunity rather than being treated as a generic security switch.
Conclusion
Design a rate limit from the protected action outward. Identify what repetition gives an attacker, choose the identity whose budget represents that opportunity, and add independent budgets when one identity can be changed or abused too easily.
Then choose a counting algorithm, distributed-state model, failure policy, and user-facing response that preserve that security meaning. Test the same distributed patterns your threat model assumes.
The practical question is not “How many requests per minute should this endpoint allow?” It is “Whose security budget should this request consume, and what happens when that budget runs out?”