How do I add rate limiting to my API?

An API without a rate limit answers the millionth request as eagerly as the first. That is a problem when the requests are guesses at a password, automated scraping, or a loop that triggers an expensive job, because nothing slows the caller down.

Why this matters

Rate limiting caps how many requests a client may make in a window of time. OWASP calls the underlying weakness Unrestricted Resource Consumption and ranks it among the top API security risks: when an endpoint does not limit the number or the size of the resources it will serve, an attacker can brute-force credentials, enumerate data, or exhaust your compute and your budget. The standard reply is the 429 status code, defined in RFC 6585 as Too Many Requests. That same document says a 429 response may include a Retry-After header telling the client how long to wait before trying again, and a well-behaved client will honour it. Limiting is not the same as authentication. It protects an endpoint even from callers who are already logged in, and it absorbs accidental traffic spikes as well as deliberate attacks, so it belongs on public routes and on login and password-reset paths in particular.

How to fix it

Apply a limit as close to the entry point as you can. A library such as express-rate-limit puts a cap on a route or on the whole application and sends 429 by default once the limit is exceeded; it is configured with a time window in milliseconds and a maximum number of requests, and those settings are what you tune. At the edge, a Cloudflare rate limiting rule can match a path or an expression and throttle or block the traffic before it reaches your origin, which also protects you from volumetric floods. Nginx can do the same on your own server with its request-limiting module, allowing a steady rate plus a small burst.

// Express: 100 requests per 15 minutes per IP
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  limit: 100,
  standardHeaders: true,
});
app.use('/api/', limiter);

# Nginx: allow 10 requests per second, with a burst of 20
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
location /api/ {
    limit_req zone=api burst=20 nodelay;
}

How to verify the fix

Send more requests than the limit in a short window and confirm the endpoint starts answering 429 instead of 200. Check whether the response carries a Retry-After header and the rate-limit headers the middleware adds. Test from a single client so the limit is actually exercised, and confirm that ordinary traffic under the threshold still succeeds, so you have not locked out legitimate users.

for ($i=1; $i -le 120; $i++) { curl -s -o NUL -w "%{http_code}\n" https://example.com/api/ping }

Automated check

The Auditor does not test rate limiting. A scan sends a small number of ordinary requests to read headers and content, so it cannot tell whether repeated or abusive traffic would be throttled, and it will not raise a finding for a missing limit. Confirm the 429 behaviour yourself with the loop above, then use the scan for the response-header and configuration issues it does cover.

Scan your app free →

Related issues