Quick answer
Slowloris is a low-bandwidth denial-of-service attack that holds many connections to a web server open by sending partial HTTP requests very slowly and never completing them. It sends a fragment of a request, then dribbles one more header every few seconds so the connection never times out or finishes. A server that dedicates a worker to each connection runs out of them, and real visitors get nothing. It is neutralised cheaply with request timeouts and a buffering reverse proxy.
01What is a Slowloris attack?
Slowloris is an application-layer denial-of-service attack that exhausts a web server's connection capacity rather than its bandwidth. It was published by Robert "RSnake" Hansen in 2009 and named after the slow-moving primate.
The idea is to hold connections open as long as possible while doing as little as possible. The tool opens a TCP connection, sends an incomplete HTTP request — the request line and maybe one header, but never the blank line that signals the end of the headers — and then, just before the server would give up, sends one more header line. The request is never completed, so the server keeps the connection in its "reading request" state, waiting.
Repeat that across hundreds of connections and a server that handles one connection per worker has every worker parked, waiting for requests that will never finish. As the original author put it, it is "the equivalent of a SYN flood but over HTTP" — except it uses complete TCP connections and partial HTTP requests, so it survives defences aimed at the packet layer.
02Slow headers and slow bodies (R.U.D.Y.)
There are two ways to be slow, and attackers use both.
- Slow headers exploit servers that wait for the whole header block before processing, holding a worker the entire time
- R.U.D.Y. ("R-U-Dead-Yet") announces a large body via Content-Length and then feeds it at a crawl, tying up the body-reading stage instead
- Slow read inverts it: the client advertises a tiny receive window and drains the response slowly, so the server's send buffer stays occupied
- All three share the signature of many simultaneous connections that transfer almost no data and never complete
| Variant | What is dribbled | Keeps the server in |
|---|---|---|
| Slowloris (slow headers) | One header line every few seconds, never the final blank line | "Reading request headers" state |
| R.U.D.Y. (slow body) | A large Content-Length body sent one byte at a time | "Reading request body" state |
| Slow read | Requests a large response, then reads it a byte at a time | "Sending response" state |
03Why thread-per-connection servers suffer
The attack works precisely against servers that assign a dedicated process or thread to each connection and cap how many they will run. Apache's prefork and worker models are the textbook example: MaxRequestWorkers defaults to 256 for prefork and 400 (16 x 25) for the threaded MPMs. Park that many connections in a half-sent request and there is nothing left to serve anyone else.
Event-driven servers that multiplex thousands of connections in a single process — handling each with a small amount of state rather than a whole thread — are far more resilient, because an idle, half-open request costs them almost nothing. But even they have limits, and the real defence is not architecture alone: it is refusing to wait indefinitely for a request that is not arriving.
04The fix: timeouts and connection limits
The cure is to stop being patient. Set a hard limit on how long a client may take to send its request, cap how many connections one address may hold, and reset the ones that blow the limit. The configuration below was syntax-checked with `nginx -t` and then run against simulated slow-header and slow-body clients in an isolated namespace: slow-header clients that added a header line every second were still reset at the 10-second header timeout (logged as 408), and slow-body clients beyond the per-address cap of 20 were refused with 503. It is illustrative, not a drop-in production file; nginx DDoS protection covers the wider set of limits, buffers and caching.
- `client_header_timeout` is the single most important setting against classic Slowloris — it bounds the slow-header stage (nginx default is 60s)
- `client_body_timeout` cuts R.U.D.Y. clients that pause longer than the timeout; it measures the gap between two reads, not the whole body, so a client that sends a byte inside every window survives it
- `send_timeout` limits the slow-read variant the same way, by bounding gaps between response writes
- `limit_conn` stops one address hoarding connections in the body and response stages; nginx only counts a connection once its whole request header has been read, so the slow-header stage is left to `client_header_timeout`
- `reset_timedout_connection on` frees the socket immediately rather than leaving it in a lingering close state
http {
# the whole request header must arrive within this window
client_header_timeout 10s;
# max gap between two reads of the request body
client_body_timeout 10s;
# max gap between two writes of the response (blunts slow-read)
send_timeout 10s;
# idle keep-alive connections are closed after this
keepalive_timeout 15s;
# send a TCP reset on timeout instead of lingering
reset_timedout_connection on;
limit_conn_zone $binary_remote_addr zone=peraddr:10m;
server {
# cap concurrent connections per client address
limit_conn peraddr 20;
}
}05Slow POST (R.U.D.Y.): stalling the request body
A slow-POST client sends complete, valid headers, so the header timeout never fires. It announces a body with Content-Length, usually against a form, an upload or an API route, and then feeds it in a few bytes at a time, each arriving just inside the server's read window. As the list above notes, a body timeout that measures the gap between two reads only catches clients that pause for longer than that window.
Two other controls do more of the work. A size limit refuses an absurd announced body before any of it is read: nginx answers 413 when the declared size exceeds client_max_body_size, which defaults to 1 MB. A minimum data rate cuts a client off however regular its bytes are: Apache's mod_reqtimeout default, body=20,MinRate=500, allows 20 seconds, adds one second for every 500 bytes received, and answers 408 to a client that falls behind. HAProxy's timeout http-request covers only the headers unless option http-buffer-request is set, in which case it bounds the body as well.
06Slow read, and the timeouts for each stage
Slow read attacks the other end of the exchange. The client sends an ordinary request, advertises a tiny TCP receive window and reads the reply as slowly as it can, so the server's send buffer stays full and the connection stays open while it waits. Large responses give it more to drain. The open-source slowhttptest tool implements slow headers, slow bodies and slow read side by side, so operators can test each against servers they own.
Mainstream servers bound this stage only with an inactivity timer, so a connection that makes some progress inside each window survives however long the whole response takes. The per-address connection limit above, and serving large files from a cache rather than the origin, are the practical caps. The table lists the timer for each stage on nginx, Apache httpd and HAProxy, with their documented defaults; Apache's values apply when mod_reqtimeout is loaded.
| Stage (attack) | nginx | Apache httpd | HAProxy |
|---|---|---|---|
| Request headers (Slowloris) | client_header_timeout, default 60s for the whole header | RequestReadTimeout header=20-40,MinRate=500 | timeout http-request; if unset, only the inactivity timer (timeout client) applies |
| Request body (R.U.D.Y.) | client_body_timeout, default 60s between two reads; client_max_body_size, default 1m | RequestReadTimeout body=20,MinRate=500 | timeout http-request with option http-buffer-request, otherwise timeout client |
| Response (slow read) | send_timeout, default 60s between two writes | TimeOut, default 60s, waiting for the client to acknowledge | timeout client, an inactivity timer |
| Idle keep-alive | keepalive_timeout, default 75s | KeepAliveTimeout, default 5s | timeout http-keep-alive, otherwise timeout http-request |
07How to detect a Slowloris attack
The detection signature is distinctive: a high number of concurrent connections from relatively few addresses, each transferring almost no data and sitting in a request-reading state for far longer than any real browser would. A spike in 408 (Request Timeout) responses once your timeouts engage is corroboration.
Both checks below were run on Debian 12 against the test setup described above. The first counted 23 held-open connections from the single simulated attacker address (3 slow-header and 20 slow-body clients); the second counted the three slow-header clients cut off by the header timeout.
# established connections to the web ports, per client address
ss -Htn state established '( sport = :80 or sport = :443 )' \
| awk '{print $4}' | sed 's/:[0-9]*$//' | sort | uniq -c | sort -rn | head
# requests that timed out while being read (default combined log format)
awk '$9 == 408' /var/log/nginx/access.log | wc -l08Why a reverse proxy neutralises Slowloris
The structural defence is a buffering reverse proxy. A proxy that does not forward a request to your origin until it has received the complete request absorbs the slow dribble on its own connection and shields the origin entirely — the origin only ever sees whole, fast requests. Combined with large connection capacity and aggressive timeouts, this removes the attack. Behind a reverse proxy such as GuardX, a Slowloris aimed at the site is absorbed at the edge and never ties up your server.
The honest caveat is the recurring one: this protects traffic that arrives through the proxy. An attacker who can reach your origin address directly can run Slowloris straight at it, which is why the server-side timeouts above still matter as defence in depth, and why hiding the origin IP is part of the picture. For the general incident playbook, see how to stop a DDoS attack; for where this sits among other threats, the types of DDoS attacks overview.
Questions
Frequently asked questions
Sources
Sources and verification
nginx configuration syntax-checked with nginx -t (nginx 1.22.1) and load-tested against simulated slow-header and slow-body clients in an isolated network namespace; the ss (iproute2) and awk detection commands run against that test; default values quoted from the nginx, Apache httpd and HAProxy 3.2 documentation; on Debian 12, October 2026.
- 01Slowloris HTTP DoS (R. Hansen, original write-up, archived) · ha.ckers.org (Internet Archive)
- 02CVE-2007-6750: Apache HTTP Server partial-request DoS (Slowloris) · NVD
- 03nginx ngx_http_core_module (client_header_timeout, keepalive_timeout) · nginx.org
- 04nginx ngx_http_limit_conn_module · nginx.org
- 05Apache mod_reqtimeout (RequestReadTimeout defaults) · Apache
- 06Apache mpm_common (MaxRequestWorkers defaults) · Apache
- 07Apache core (TimeOut, KeepAliveTimeout) · Apache
- 08HAProxy 3.2 Configuration Manual (timeout http-request, timeout client, option http-buffer-request) · HAProxy
- 09SlowHTTPTest (slow headers, slow body and slow read tests) · GitHub
Last updated