Guide

Rate Limiting and Abuse Control

Part 6 of 8By Amith Kumar6 min read
Part 6 of 8Nginx in Production: A Hands-On Guide
  1. 1Install Nginx and Its Config Model
  2. 2Serving Static Sites and Server Blocks
  3. 3Reverse Proxy to an App
  4. 4HTTP/2, HTTP/3 and Compression
  5. 5Caching, Static and Proxy
  6. 6Rate Limiting and Abuse Control
  7. 7Load Balancing App Instances
  8. 8Hardening, Headers and Logging
Verified 22 Jul 2026 · Ubuntu 24.04 LTS · nginx 1.24.0

Why one abusive client should not take down your app

Fast and cached does not help if one client floods you, so rate limiting is the guard on the door. Rate limiting caps how many requests a single client can make in a window, so a bot hammering your login page or a broken script in a loop cannot exhaust your application. Nginx does this with a leaky-bucket algorithm that is precise and cheap. This chapter sets it up, tunes the burst behaviour that everyone gets wrong, and points it at the endpoints that actually need it.

Key takeaways
  • limit_req_zone defines a shared memory zone keyed by client IP and a sustained rate; limit_req applies it to a location.
  • rate is the steady allowance; burst is how many extra requests may queue before Nginx starts rejecting with 503.
  • nodelay serves the burst immediately instead of spacing it out, which is what real users want, so burst plus nodelay is the usual production choice.
  • Rate limit the endpoints that get abused, login, signup, search, write APIs, not your whole site, or you will throttle legitimate browsing.

Every public server gets hammered. Some of it is malicious, credential-stuffing your login, scraping your API. Some of it is accidental, a client with a retry loop and no backoff. Either way, a single source sending thousands of requests a second can tie up the workers your real users need. Rate limiting is how you let the many normal clients through while capping any single one, and Nginx has it built in with an algorithm that costs almost nothing.

Rate limiting with the leaky bucket, in one config

Nginx rate limiting uses a leaky bucket: each client has a bucket that drains at a steady rate, and requests that arrive faster than it drains overflow and are rejected. You define the bucket once, in the http context, and apply it where you need it.

limit_req_zone $binary_remote_addr zone=api:10m rate=5r/s;

$binary_remote_addr keys the bucket by client IP, in a compact binary form. zone=api:10m names a 10 MB shared memory zone, which holds state for roughly 160,000 IPs, plenty for one server. rate=5r/s is the sustained rate: five requests per second per IP. Then apply it to a location:

location /login {
    limit_req zone=api;
    proxy_pass http://127.0.0.1:3000;
}

With just this, a client gets five requests per second and anything faster receives a 503 Service Temporarily Unavailable. That is strict, and for a login endpoint that strictness is a feature.

burst and nodelay, the part everyone gets wrong

Pure rate=5r/s is often too rigid, because real browsing is bursty: a user loads a page and it fires ten requests at once for the page and its assets. Reject those and the site feels broken. burst solves this by allowing a queue:

limit_req zone=api burst=10 nodelay;

burst=10 lets up to ten extra requests queue beyond the steady rate before Nginx starts returning 503. Without nodelay, those queued requests are released slowly, at the configured rate, so the tenth one waits two seconds. That protects the backend but makes the page feel sluggish. nodelay changes that: the burst is served immediately, and only genuinely sustained excess gets rejected. For user-facing endpoints you almost always want burst plus nodelay: normal bursts pass instantly, and only a client that keeps flooding past the burst allowance sees a 503.

The mental model to keep: rate is the long-run average you permit, and burst is how much short-term spikiness you tolerate before saying no. Tune them to your endpoint. A login form might be rate=1r/s burst=5; a public API rate=20r/s burst=40. Set them from what legitimate use actually looks like, not a guess.

When the limit is exceeded, Nginx returns 503 and writes a clear line to the error log:

limiting requests, excess: 5.000 by zone "api", client: 203.0.113.10

That log line is how you confirm the limit is doing its job and which clients are hitting it. You can change the rejection status with limit_req_status 429 so clients receive the more correct "Too Many Requests," which well-behaved clients treat as a signal to back off.

Checkpoint

Hammer the limited endpoint faster than the rate and you should start getting 503 (or 429) responses, with a limiting requests line in /var/log/nginx/error.log naming the client. Normal browsing should never trip it; if real users see 503s, your rate or burst is too tight.

Limit the right endpoints

The most common mistake is rate limiting everything, including static assets and normal page loads, which throttles the very users you want. Rate limiting is a scalpel, not a wall. Apply it where abuse concentrates:

  • Login and signup, to blunt credential stuffing and mass-registration.
  • Search and anything expensive, to stop one client monopolising your database.
  • Write APIs, POST, PUT, DELETE, where each request has real cost.

Leave normal page and asset serving unlimited, or generously limited, so browsing stays fast. You can even define two zones, a tight one for sensitive endpoints and a loose one for general traffic, and apply each where it fits.

Going further: connection limits

limit_req caps request rate; limit_conn caps concurrent connections from one IP, which stops a single client from opening hundreds of parallel downloads and starving others:

limit_conn_zone $binary_remote_addr zone=conns:10m;
limit_conn conns 10;

Ten simultaneous connections per IP is plenty for a browser and a hard ceiling for a scraper trying to parallelise. Rate limiting and connection limiting together cover the two ways a single client overwhelms a server: too many requests over time, and too many at once.

Frequently asked questions

What is the difference between burst and rate?

rate is the steady allowance Nginx permits over time, such as 5r/s. burst is how many extra requests may queue beyond that rate before Nginx starts rejecting with 503, which absorbs the normal spikes of a page loading many assets at once.

What does nodelay actually change?

Without nodelay, queued burst requests are released slowly at the configured rate, so the page feels sluggish. With nodelay the burst is served at once, and only a client that keeps flooding past the allowance gets a 503, which is what real users want.

Should I rate limit my whole site?

No. Limiting static assets and normal page loads throttles the users you want to keep. Point the limits at endpoints that get abused, like login, signup, search, and write APIs, and leave general browsing unlimited or generously limited.

How do I return 429 instead of 503 when a client is limited?

Set limit_req_status 429 so rejected requests carry "Too Many Requests" instead of the default 503. Well-behaved clients treat a 429 as a signal to slow down and retry later rather than hammering on.

Recap and what is next

You can cap how fast and how many any single client hits your server, absorb the normal bursts of real browsing with burst and nodelay, and point the limits precisely at the endpoints that get abused instead of throttling everyone. nginx's leaky bucket does this for a rounding error of CPU. Your front door now holds under a flood.

Next we go the other way and scale up, spreading traffic across several copies of your app with load balancing, so capacity grows and a single crash stops being an outage.

Guard your app on a slice

Rate limiting is one block in the same nginx config you have been building. Launch a slice, put your app behind it, and cap the abusive clients while real users pass through.


Put it live on your own slice

Run your site behind Nginx on a slice

Nginx needs a server with a public IP to be your front door. Spin up a slice, put your app behind the hardened reverse proxy from this guide, and serve it fast and safe.

Get early access

This guide is provided for educational purposes and offered as is, without warranty of any kind. The commands change the configuration of a server you control, and some can lock you out if run out of order. Test on a non-production machine, keep a second session open, and take a snapshot or backup before you begin. You are responsible for changes made to your own systems; ServerCake accepts no liability for any loss, downtime, lockout, or damage arising from following this guide or running the accompanying script.

Was this guide helpful?
Share

Spotted something out of date or have a question?

Built by the ServerCake team

Cloud that speaks India.

ServerCake is India's KYC-verified cloud: VMs and managed databases priced in ₹, billed with GST, on India-resident infrastructure. Reserve your spot for early access.

Reserve your spot