Nginx is very good at holding open connections cheaply, which is why it survives floods that kill an application server. That strength is also a trap: it will happily accept far more concurrent work than the thing behind it can handle, and then pass the failure downstream.
Limit Connections Before Limiting Requests
Two different limits solve two different problems. A connection limit caps how many sockets a single address can hold open, which is the defence against slow attacks that occupy connections without sending much. A request rate limit caps how fast an address can ask for work, which is the defence against volume. Sites that configure only the second one remain vulnerable to the first.
The Timeouts That Matter
- Header and body read timeouts: the single most effective control against slow-send attacks. Defaults measured in minutes are an invitation.
- Keepalive timeout: generous keepalives improve real user performance and multiply the cost of a connection flood. Tune with both in mind.
- Upstream timeouts: if the application stalls, nginx should give up quickly rather than accumulate waiting workers.
Buffering is a defence
When nginx buffers a full request before opening an upstream connection, a slow client occupies cheap nginx memory instead of an expensive application worker. Turning buffering off for convenience removes that shield.
Rate Limits That Do Not Break Real Users
A single global rate limit is almost always either too strict for a busy page or too loose to matter. Apply different limits per class of endpoint: static assets can be generous, HTML pages moderate, and expensive endpoints — search, login, anything that writes — tight. Allow a burst so that a normal page load, which fires many requests at once, is not penalised for behaving like a browser.
Return the Right Status
When a limit trips, answering with 429 tells well-behaved clients and search engines to back off and return. Answering with a generic error teaches crawlers that the page is broken. The status code you choose during an attack has consequences that outlive the attack.
Know the Ceiling
All of this operates on traffic that already reached your machine. Kernel-level and upstream filtering exist because at a certain volume, the cheapest possible handling of a packet is still too expensive when there are millions of them. Tuning nginx raises the ceiling; it does not remove it.