Free — no signup required

Rate Limiting: Preventing Abuse at Scale

3 min read

The "Unlimited" Problem

APIs are designed to be called by machines, which means they can receive thousands of requests per second without any human fatigue slowing the attacker down. Without limits, an API is vulnerable to several classes of abuse:

  1. Brute Force Attacks: Systematically guessing passwords (POST /api/login with 10,000 password attempts) or enumerating IDs (GET /api/receipts/1, /api/receipts/2, ... /api/receipts/100000).
  2. Denial of Service (DoS): Flooding the server with requests until it runs out of resources and stops serving legitimate users.
  3. Cost Amplification: If an API endpoint triggers an expensive operation — a third-party SMS, an AI inference call, a complex database query — an attacker can weaponize it to run up your bill.
  4. Credential Stuffing: Automatically testing leaked username/password pairs from other breaches against your login endpoint.

Rate Limiting Strategies

Rate limiting controls how many requests a client can make in a given time window. The "client" is typically identified by IP address, API key, or authenticated user ID.

Strategy How It Works Weakness
Fixed Window "100 requests per hour, reset at :00" Attacker sends 100 at 11:59 and 100 at 12:00 — 200 requests in 2 minutes
Sliding Window "100 requests in the last 60 minutes" More complex to implement, but eliminates the boundary exploit
Token Bucket A bucket fills with tokens at a fixed rate; each request consumes one token Allows controlled bursts up to bucket capacity
Leaky Bucket Requests queue and are processed at a constant rate; overflow is dropped Smooths traffic but adds latency for bursty legitimate clients

For most APIs, a sliding window or token bucket approach provides the best balance of protection and usability.

The Correct Response

When a client exceeds the rate limit, the API must return HTTP status 429 Too Many Requests. The response should include a Retry-After header (in seconds) or X-RateLimit-Reset (Unix timestamp) so well-behaved clients know when to retry.

HTTP/1.1 429 Too Many Requests
Retry-After: 30
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1700000030

{
  "error": "Rate limit exceeded. Please retry after 30 seconds."
}

A critical architectural point: rate limiting must be enforced at the API Gateway or reverse proxy layer (e.g., Kong, AWS API Gateway, Nginx), not inside individual application services. If you implement rate limiting only in your Node.js app, an attacker who bypasses the gateway hits unprotected services directly. Centralized enforcement also means you can apply limits consistently across all services without duplicating logic.

Interview Tip

A common interview question: "How would you prevent brute force attacks on a login endpoint?" Weak answer: "Add CAPTCHA." Strong answer: "Layer multiple controls: rate limit by IP (e.g., 5 failed attempts per minute) at the API gateway, implement account lockout after N failures with exponential backoff, add CAPTCHA after the first failure, monitor for distributed attacks across many IPs using anomaly detection, and use 429 with Retry-After to signal limits to clients. No single control is sufficient."

This is one of 18 chapters

Every chapter is free — Kubernetes, Terraform, SRE, distributed systems, and more — with fast daily review built in.

Sign up free