Most DDoS attacks are obvious: they generate enormous traffic spikes that saturate links and bring servers down in seconds. Low and slow attacks are the opposite. They use tens — not thousands — of connections, each transmitting data at a crawl, holding server resources indefinitely without ever completing the request. Volume-based detection tools simply can’t see them.
Low and slow attacks are a category of Layer 7 DDoS attacks that exhaust web server resources through a small number of connections held open for long periods, transmitting data at a minimal rate. Instead of flooding with volume, the attacker occupies server threads or file descriptors waiting for requests that never complete.
Why low and slow attacks go undetected
Traditional DDoS detection monitors volume: packets per second, bytes per second, requests per second. Low and slow attacks generate very little traffic. The table below illustrates the profile difference compared to a conventional HTTP Flood:
| Characteristic | HTTP Flood | Low and Slow (Slowloris) |
|---|---|---|
| Request volume | 500,000+ req/s | 1–10 req/s |
| Generated bandwidth | 50+ Gbps | Less than 1 Mbps |
| Simultaneous connections | Thousands (short-lived) | 100–1,000 (long-lived) |
| Data per connection/time | High continuous volume | 1 byte every 10–15 seconds |
| Volumetric visibility | High — easy to detect | Virtually none |
| RPS (Requests Per Second) | Abrupt spike | Stable and low |
| ESTABLISHED connections | Fluctuate rapidly | Accumulate continuously |
The server keeps each connection alive — consuming a thread or a file descriptor — while waiting for the request to complete. With enough connections occupied, the thread pool exhausts and legitimate connections receive HTTP 408 Request Timeout or are refused with a connection error.
Slowloris — exploiting the CRLF mechanics of HTTP/1.1
Developed by Robert “RSnake” Hansen in 2009, Slowloris exploits a fundamental characteristic of the HTTP/1.1 protocol (RFC 7230): the server must wait for the header termination sequence CRLF CRLF (\r\n\r\n) before processing the request. While that sequence doesn’t arrive, the server keeps the connection open and resources allocated.
The mechanism works in five steps:
- The attacker opens hundreds of TCP connections to the target server.
- On each connection, it sends the beginning of a syntactically valid HTTP/1.1 request — the request line and a few headers — but deliberately omits the CRLF CRLF sequence that would terminate the header block.
- Every 10–15 seconds, it sends one additional byte (a new partial header, such as
X-Keep: a) to reset the server’s idle timeout, keeping the connection alive without completing the request. - The server, following the HTTP/1.1 specification, waits indefinitely for the CRLF CRLF that never arrives. Each connection occupies a thread in Apache (MPM Prefork or Worker model) or a file descriptor in asynchronous servers.
- When all available threads or connections are occupied, new legitimate requests are refused or receive HTTP 408 Request Timeout.
The periodic transmission of additional bytes is the critical element: it prevents the server from closing the connection due to inactivity, extending the attack indefinitely at minimal bandwidth cost to the attacker.
R.U.D.Y. — POST body fragmentation and socket starvation
R.U.D.Y. (R-U-Dead-Yet?) operates at a different phase of the HTTP protocol: the POST request body, after headers have already been correctly sent.
The mechanism exploits the Content-Length field:
- The attacker sends a complete and syntactically valid POST request, with all headers, including
Content-Length: 1000000(or another large value). - The server allocates a socket and reserves resources to receive the declared 1 MB body.
- The attacker transmits the body in fragments of 1 byte every 10 seconds — valid traffic from the TCP protocol perspective, violating no inactivity timeout.
- The socket remains locked in receive allocation. With dozens of simultaneous connections like this, available sockets are exhausted.
R.U.D.Y.’s preferred targets are endpoints that process large POST data volumes: authentication forms, file upload APIs, data processing endpoints. These typically have a high or unlimited Content-Length limit, maximizing the lock-up time of each socket.
Slow Read Attack — TCP Receive Window manipulation
The Slow Read Attack operates at the transport layer (TCP), not the HTTP application layer. The mechanism exploits the TCP Receive Window — the parameter that signals to the sender how many bytes the receiver is ready to accept.
The attack works as follows:
- The attacker establishes a normal TCP connection and sends a legitimate HTTP request.
- Before the server begins sending the response, the attacker sets its TCP Receive Window to a very low value — close to zero (win=1 or win=64), instead of the standard 65 KB–128 KB.
- The server, respecting TCP flow control, can only send data in the amount the receiver’s window authorizes. With a 1-byte window, it sends one byte, waits for the ACK, sends another byte, and so on.
- The server’s send buffer (typically 65 KB–128 KB) fills up because data cannot be dispatched at normal speed.
- When the send buffer saturates, the server emits TCP Window Probes — periodic probe packets to check whether the client’s window has been expanded. Each probe consumes CPU and keeps connection state active in the kernel.
- Dozens of connections with reduced windows simultaneously consume both the send buffer pool and CPU from probe processing, exhausting server resources without generating visible traffic.
The Slow Read Attack is particularly effective against servers that have no time limit for sending complete responses — only for receiving requests.
Apache Killer — Range header and response fragmentation
The Apache Killer sends requests with a Range header containing hundreds of overlapping and redundant byte ranges. The server must prepare and serialize each range individually as a multipart response part, consuming CPU and memory per request even from a single client.
GET / HTTP/1.1Host: target.comRange: bytes=0-1,0-2,0-3,0-5,0-10,...,0-65536This attack was patched in Apache 2.2.21 (2011) with a limit on accepted ranges per request, but variations still affect unpatched servers.
Concurrency models and attack surface
The effectiveness of low and slow attacks varies according to the server concurrency model:
| Server | Concurrency model | Critical limit | Slowloris vulnerability | Slow Read vulnerability |
|---|---|---|---|---|
| Apache MPM Prefork | Thread per connection | MaxRequestWorkers (default: 150–256) | High — 1 thread per stuck connection | Medium |
| Apache MPM Worker | Thread per connection | MaxRequestWorkers | High | Medium |
| IIS (Windows) | Fixed thread pool | maxConcurrentRequestsPerCPU | High | Medium-High |
| Nginx | Asynchronous (event loop) | worker_connections × worker_processes | Low — doesn’t block a thread, but exhausts file descriptors | Medium — send buffers fill up |
| Node.js | Async event loop | maxConnections (net.Server) | Low | Medium |
| LiteSpeed | Asynchronous | Configurable | Low | Low |
Important note about Nginx: although Nginx’s asynchronous architecture is significantly more resistant to classic Slowloris — as it doesn’t block a thread per connection — the server is still limited by the worker_connections directive. Each open connection, even idle, consumes a file descriptor. An attacker with sufficient connection volume can exhaust Nginx’s total worker_connections (default: 1,024 per worker), causing new connection refusals without any thread blocking.
HTTP/2, stream multiplexing, and new vectors
HTTP/2 changed the dynamics of low and slow attacks in an ambivalent way:
Resistance to classic Slowloris: since HTTP/2 uses multiplexing — multiple logical streams over a single TCP connection — Slowloris that opens hundreds of TCP connections is less effective. The server needs fewer connections to serve the same number of requests.
New slow attack vectors: multiplexing creates HTTP/2-specific attack surfaces:
- Slow streams via WINDOW_UPDATE manipulation: HTTP/2 has its own flow control, independent of TCP. An attacker can open multiple streams on a single TCP connection and send
WINDOW_UPDATEframes with minimal increments — forcing the server to send data from each stream in tiny fragments. The effect is similar to the Slow Read Attack, but operating at the HTTP/2 application layer. - SETTINGS frame manipulation: the attacker can send
SETTINGSframes with aggressive parameters (such asINITIAL_WINDOW_SIZEclose to zero) for all streams, reducing the throughput of the entire connection while keeping the channel open. - Stream multiplexing exhaustion: opening dozens of simultaneous streams on a single connection and feeding them slowly consumes stream state buffers on the server without triggering TCP connection alerts.
Nginx with HTTP/2 enabled is still limited by worker_connections — each TCP connection with multiple HTTP/2 streams still occupies a file descriptor. Protection requires configuring stream limits per connection and stream timeouts beyond TCP connection timeouts.
Mitigation techniques
Aggressive per-protocol-phase timeouts
Configuring timeouts per phase — differentiating the time to receive headers, body, and send response — is the most direct and efficient defense:
# Nginx — Slowloris and R.U.D.Y. mitigationclient_header_timeout 10s; # Max time to receive complete headers (CRLF CRLF)client_body_timeout 10s; # Max time between consecutive body byteskeepalive_timeout 15s; # Max idle keepalive connection timesend_timeout 10s; # Max time between bytes sent to client# Apache — mod_reqtimeout for Slowloris and R.U.D.Y.# Headers: minimum 500 bytes/s, deadline of 10 to 20 secondsRequestReadTimeout header=10-20,minrate=500# Body: minimum 500 bytes/s, deadline of 20 secondsRequestReadTimeout body=20,minrate=500The minrate directive is especially relevant: instead of a fixed timeout, it checks whether the transfer rate is above a minimum threshold. A connection sending 1 byte every 10 seconds (1 byte/10s = 0.1 bytes/s) violates minrate=500 immediately, being terminated even before reaching the maximum deadline.
Per-IP connection limits with limit_conn
# Nginx — simultaneous connection limiting per IPlimit_conn_zone $binary_remote_addr zone=conn_limit:10m;limit_conn conn_limit 20; # Maximum connections per IPlimit_conn_status 429; # Returns HTTP 429 when limit is exceededNginx’s limit_conn acts at the TCP active connection level, not the request level. This makes it effective against Slowloris (which opens many connections) even before any HTTP data is received.
Request Buffering at the edge reverse proxy
Request Buffering is a fundamental defensive mechanism that operates at the edge reverse proxy: the edge node assembles and validates the complete HTTP request — including all headers and the declared body — before opening any upstream connection to the origin server.
The impact is decisive:
- The origin server never sees incomplete connections. Only fully received and edge-validated requests reach the backend.
- Slowloris and R.U.D.Y. attacks are held at the edge, which applies its own aggressive timeouts.
- The edge supports orders of magnitude more simultaneous connections than a typical Apache server, diluting the attack impact.
- The Slow Read Attack is also neutralized: the edge sends the response to the client asynchronously, without keeping the origin server’s send buffer state locked while waiting for the client to slowly receive data.
JA3/JA4 Fingerprinting and behavioral analysis at the WAF
Attack tools like Slowloris, R.U.D.Y., and their variants exhibit identifiable patterns that differ from legitimate browsers:
TLS Fingerprinting (JA3/JA4): each client’s TLS handshake leaves a “fingerprint” based on the sequence of cipher suites, TLS extensions, and version parameters. The JA3 hash (and its successor JA4) of attack tools like Python or Perl Slowloris differs consistently from the hash of browsers like Chrome, Firefox, or Safari. A WAF with JA3/JA4 support can block requests with known attack tool fingerprints before even analyzing any HTTP data.
Complementary behavioral analysis:
- Absence of typical browser headers (
Accept-Language,Accept-Encoding,Accept) - No requests for secondary resources (CSS, JavaScript, images) after the initial request
- Mechanical and regular timing between bytes — non-human pattern
- Request sequences incompatible with real browsing behavior — patterns detectable through bot detection techniques
HTTP/2 stream limit configurations
# Nginx — limits to mitigate slow attacks in HTTP/2http2_max_concurrent_streams 64; # Maximum simultaneous streams per connectionhttp2_idle_timeout 30s; # Timeout for idle HTTP/2 connectionshttp2_recv_timeout 30s; # Timeout for receiving HTTP/2 framesDetection signals
| Indicator | What to look for | Likely attack |
|---|---|---|
| High ESTABLISHED connections with low RPS | ss -s or netstat -an showing abnormally high count vs. low HTTP throughput | Slowloris |
| Long-duration connections without completing | Logs showing connections open for 30s+ without a completed request | Slowloris / R.U.D.Y. |
| Few IPs with many connections each | Small number of IPs with dozens of simultaneous open connections | Slowloris |
| Normal CPU, exhausted threads or file descriptors | Server with no CPU load but refusing connections | All |
| 503 or 408 errors without RPS spike | Service Unavailable or Request Timeout without visible traffic increase | All |
| Server send buffer continuously full | Network metrics showing TCP backpressure without volumetric traffic | Slow Read Attack |
| Frequent TCP Window Probes in network logs | Packet captures (tcpdump/Wireshark) showing periodic probes | Slow Read Attack |
Common mistakes and solutions
Mistake: Using only volumetric detection to monitor for DDoS. Solution: Low and slow attacks are invisible to volume alerts. Also monitor: connections per IP, average connection duration, request completion rate, and thread/file descriptor utilization.
Mistake: Assuming Nginx is completely immune to Slowloris.
Solution: Nginx’s event-driven architecture is more resistant, but not immune. The worker_connections limit can still be exhausted with sufficient slow connections. Configure limit_conn and timeouts even with Nginx.
Mistake: Configuring only a single global connection timeout.
Solution: Separate timeouts by protocol phase (client_header_timeout, client_body_timeout, send_timeout). A single global timeout does not catch connections that continuously send data at a minimum rate — Apache’s minrate is the most precise mechanism for that case.
Mistake: Ignoring HTTP/2 as a slow attack vector because it’s more resistant to classic Slowloris.
Solution: HTTP/2 doesn’t eliminate low and slow vectors — it transforms them. Configure stream limits (http2_max_concurrent_streams) and HTTP/2-specific timeouts beyond TCP timeouts.
Mistake: Not monitoring the thread pool or file descriptor usage. Solution: Thread or file descriptor exhaustion is the most direct indicator of an ongoing low and slow attack. Configure alerts when utilization reaches 80%.
Frequently asked questions
Why is Slowloris called a “stealthy” attack? Because it generates negligible traffic volume — less than 1 Mbps — that doesn’t trigger volumetric alerts. It is invisible to volume-based IDS/IPS, to transit providers monitoring only bandwidth, and to requests-per-second dashboards. The only metric that reveals it is the number of accumulated ESTABLISHED connections.
Can a single computer execute Slowloris? Yes. Tools like the original Perl Slowloris and modern Python versions execute the attack from a single machine without a botnet. This makes it accessible to attackers with limited resources and makes IP volume or diversity-based blocking ineffective.
Does HTTPS/TLS prevent Slowloris or R.U.D.Y. attacks? No. The TLS handshake is negotiated before the HTTP layer and completes normally in both attacks. After the TLS handshake concludes, Slowloris and R.U.D.Y. operate in the HTTP layer over the established TLS channel — encryption has no visibility into the transmission rate of the HTTP payload. Effective mitigation must occur after TLS termination, at Layer 7, through header analysis, timing, and request behavior.
How long does it take for Slowloris to bring down an unprotected Apache server? In documented tests, an Apache server with default configuration (256 threads) can be brought down by Slowloris in 30 seconds to 2 minutes, depending on the number of connections the attacker opens. With 256 simultaneous slow connections, the thread pool exhausts completely before any default timeout acts.
How do you identify if a 503 error is caused by HTTP Flood or a Low and Slow attack?
The difference lies in the RPS and ESTABLISHED connection metrics. An HTTP Flood causes a 503 error accompanied by an abrupt and high spike in requests per second (RPS), with high CPU and network consumption. A Low and Slow attack generates 503 errors with stable, low RPS — practically indistinguishable from normal traffic — but with a growing and anomalous number of long-duration ESTABLISHED connections, near-normal CPU, and exhausted threads or file descriptors. The command ss -s or netstat -an | grep ESTABLISHED | wc -l cross-referenced with current RPS is the fastest diagnosis.
Is R.U.D.Y. more dangerous than Slowloris?
It depends on the target. R.U.D.Y. is more effective against applications that process large POST data volumes — uploads, APIs, forms with high Content-Length limits. Slowloris is more generic and affects any HTTP server with a thread or connection limit. On servers with mod_reqtimeout correctly configured, R.U.D.Y. is mitigated by minrate, while Slowloris requires additional client_header_timeout configuration.
How do you distinguish a user with a slow connection from a low and slow attack? Users with legitimate slow connections eventually complete the request, request multiple resources sequentially (CSS, JS, images after HTML), and have variable and irregular timing between bytes — human behavior. Low and slow attacks keep connections open indefinitely without completing, with mechanical and regular timing between bytes, no requests for secondary resources, and JA3/JA4 fingerprint patterns consistent with automation tools rather than real browsers.
What is Request Buffering and why is it effective against low and slow attacks? Request Buffering is the mechanism by which an edge reverse proxy accumulates and validates the entire HTTP request — complete headers and body — before opening the connection to the origin server (upstream). The result is that the origin server never receives incomplete requests: the edge absorbs all client slowness, applies its timeouts, and only forwards to the backend what is completely received and valid. This makes the origin server immune to Slowloris and R.U.D.Y., regardless of its internal configuration.
What is the impact of HTTP/2 on low and slow attacks?
HTTP/2 uses stream multiplexing, which reduces the effectiveness of classic Slowloris that opens many TCP connections. However, it creates new vectors: an attacker can open multiple HTTP/2 streams on a single TCP connection and manipulate WINDOW_UPDATE and SETTINGS frames to force the server to transmit data in minimal fragments — replicating the Slow Read Attack effect at the application layer. Limits on http2_max_concurrent_streams and stream timeouts are required to mitigate these vectors.
How to implement on Azion
Azion neutralizes low and slow attacks before they reach origin:
- Request Buffering at the edge: Azion’s data centers assemble and validate each complete HTTP request before opening the connection to the origin server. Connections that don’t complete headers or body within configured limits are discarded at the edge, never impacting the backend.
- Aggressive per-phase timeouts: The edge applies distinct timeouts for header reception, body reception, and response delivery, terminating slow connections before they exhaust downstream resources.
- Per-IP connection limits: The edge applies simultaneous connection limits per IP, preventing a single client from occupying dozens of resources on the origin server.
- Bot Manager with JA3/JA4 Fingerprinting: Identifies attack tools like Slowloris and R.U.D.Y. through TLS fingerprinting (JA3/JA4 hashes) and behavioral analysis, blocking suspicious connections before they complete the HTTP handshake.
- WAF with behavioral analysis: Detects anomalous transmission patterns — mechanical timing, absence of browser headers, request sequences incompatible with human behavior — complementing fingerprinting with Layer 7 analysis.
Learn more in the Azion WAF documentation.