A Slowloris attack can take a web server offline from a single laptop on a home connection. It sends almost no data, generates almost no requests per second, and looks nothing like the traffic spike your DDoS protection was bought to stop.
That is the problem. Most defenses are tuned for volume: gigabits per second, requests per second, packets per second. Slowloris wins by sending as little as possible, very slowly, for as long as possible. If your protection keys on rate, this attack walks straight past it.
The short version
- It costs the attacker almost nothing. A successful run can be a few kilobytes per second from one machine, which is why volume-based defenses have nothing to trigger on.
- It works by never finishing. Each connection sends part of a request and then trickles, so the server politely holds the connection open waiting for the rest.
- There are four variants, and hardening against one does not cover the others. A header-read timeout stops the classic version and does nothing for the slow-read variant.
- Your DDoS protection and WAF probably are not what decides this. The outcome usually comes down to your web server timeouts and whether your proxy buffers requests.
- It is invisible where you would look. Requests per second read normal, and unfinished requests often never produce a log line at all.
This is the first in a series covering each of the ten simulation types Obsidio runs, one per attack. They fail in genuinely different places, which is the reason a test programme needs coverage across the categories rather than volume in one of them. For how they fit together and how to structure a first run, start with our guide on how to test your DDoS protection.
The ten simulation types
One post each, grouped the way the platform groups them.
Connection exhaustion
Protocol-specific and scripted
- DNS Floodcoming soon
- Puppeteer Scriptfunctional load, not an attack
What a Slowloris attack actually does
Slowloris opens many parallel TCP connections to a web server and then refuses to finish any of them. Each connection sends the beginning of an HTTP request (the request line, a Host header) and then trickles one more header line every few seconds. The terminating blank line that tells the server “the request is complete, you may now process it” never arrives.
The server does the reasonable thing: it waits. It holds the connection open, keeps a worker or a slot in the connection pool assigned to it, and waits for the rest of a request that will never come. Multiply that by a few hundred connections and the pool is full. Legitimate users then get a timeout, not an error page, because the server never gets far enough to serve them.
Think of twenty people phoning a shop, each starting to place an order and then stretching every sentence out so the call never ends. The staff are polite, so they stay on the line. Nobody else can get through.
The technique is not new. The original Slowloris tool was released on 17 June 2009, and it still works, because the thing it exploits is not a bug. It is the assumption, built into how HTTP servers handle connections, that a client which has started talking intends to finish.
Which of your controls should have caught this
It is worth walking the whole chain a request passes through, because the answer is rarely the control people expect. Three groups matter: what sits in front, what absorbs whatever gets through, and whether anything tells you it happened.
Zone 1
Should any of these have stopped it?
- DDoS protection and scrubbing. Waits for a traffic spike. There is not one.
- Firewall and WAF. Inspects complete requests, and this one never completes. It helps only if it buffers the full request before passing it upstream, and plenty of deployments do not.
- Rate limiting. Counts requests. This sends far too few to cross a threshold.
- Bot detection and challenges. Never loads a page, so there is nothing to challenge.
- CDN cache. Nothing cacheable is in play, so it makes no difference either way.
Zone 2
What takes the hit?
- Load balancer or reverse proxy. Absorbs the whole attack if it buffers requests. Worth confirming that yours does.
- Connection layer. Fills with held-open sockets.
- Web server. This is where it bites. Worker slots run out unless your timeouts and a minimum transfer rate cut the connections off first.
- Application and database. Never reached. The attack stops earlier.
- Auto-scaling. Sees no load spike, so it never scales.
Zone 3
Would you find out?
- Dashboards. Requests per second look completely normal.
- A check from outside your network. The one thing that reliably catches this, if you run one.
- Logging and your SIEM. Unfinished requests often produce no access-log entry, so there may be nothing to correlate afterwards.
- Alerting. Nothing crosses a threshold.
- On-call. Nobody is paged. The service is down and the room is quiet.
Purple marks what only a test on your own infrastructure can settle. Everything else follows from how the attack works, not from how well your team configured things.
That last group is the part teams tend to discover late. A control that works but is invisible is still a finding, and so is a control that fails without anyone noticing.
Four vectors, four different failure modes
“Slowloris” is often treated as one attack. In practice, holding a connection open has several distinct forms, and a server or proxy hardened against one can still be exposed to another. Obsidio’s Slowloris simulation runs four:
- Slow header. The classic. Sends
GET /path HTTP/1.1and aHostheader, then trickles one header line per interval and never sends the terminator. The server stays parked in “reading request headers” state. - Slow POST. Sends a complete, valid
POSTheader block withTransfer-Encoding: chunked, then trickles a one-byte chunk per interval and never sends the zero-length chunk that ends the body. Headers are finished, so a header-read timeout does not catch it. - Slow read. Sends a complete, entirely valid request, then stops reading the response. The client’s receive buffer fills, so the server’s send buffer backs up and the connection cannot be retired. Nothing about the request is malformed. The attack is on the read side.
- Slow drop. Sends a valid keep-alive request, sits idle, then destroys the socket abruptly at random. This mixes reset-style churn into the connection table rather than steady occupancy, which stresses connection teardown and accounting instead of capacity.
The distinction matters when you configure a fix. A header-read timeout stops slow header and does nothing for slow read. A body timeout stops slow POST and does nothing for either. Testing one vector and declaring the class handled is how teams end up surprised.
Which servers are exposed
Susceptibility comes down to architecture. Servers that dedicate a thread or process per connection run out of them; event-driven servers multiplex connections and degrade far more gracefully.
Apache 1.x and 2.x are the canonical vulnerable case in the original disclosure, along with dhttpd and the Flask development server. Servers documented as resilient include nginx, lighttpd, Hiawatha, Cherokee and IIS. But “resilient architecture” is not the same as “safe deployment”: nginx still ships default timeouts of client_header_timeout 60s and client_body_timeout 60s, which is 60 seconds of free occupancy per connection, and an application server behind the proxy may have no such protection at all.
Check what actually terminates the connection in your stack, not what the marketing page for one component says.
How to defend against a Slowloris attack
Mitigation is about refusing to wait indefinitely, and about limiting how much one client can hold. Four controls do most of the work:
- Timeouts on partial requests. On Apache,
mod_reqtimeoutis the direct answer. Loaded, its defaults areRequestReadTimeout handshake=0 header=20-40,MinRate=500 body=20,MinRate=500. That sets a minimum transfer rate, not merely a deadline, which is what actually kills a trickle. Confirm the module is loaded; availability is not the same as being active. - Minimum transfer rate, not only a timeout. A fixed deadline can be defeated by trickling just fast enough. A rate floor cannot.
- Per-IP connection limits. Cap concurrent connections per source so one client cannot occupy a meaningful share of the pool. Effective against a single-source Slowloris, and much weaker against a distributed one, which is the case worth testing.
- A buffering reverse proxy or CDN. A proxy that fully buffers the request before forwarding it absorbs the incomplete request on its own connection table and never wakes your origin. This is strong protection, provided the origin cannot be reached directly. A proxied site with an exposed origin IP is not protected.
Each of these is a configuration value in a file somewhere, which means each of them can be wrong, stale, or overridden by a later change nobody re-checked.
What you can check yourself, and what it will get wrong
Three of those questions you can answer today without help. It is worth knowing which, and worth knowing where a cheap check misleads you.
Read the configuration. Confirm the timeout module is loaded, look at the actual values, check whether your proxy buffers. This tells you the setting exists. It does not tell you the setting holds under concurrency, which is a different claim.
Test alert routing on its own. Fire a synthetic alert and see whether it reaches the right rotation and whether anyone acts. That genuinely verifies the plumbing, and it needs no attack traffic at all.
Run a single-source test against a staging environment, with change-management sign-off, on infrastructure you own. This is legitimate and useful. It is also where the trap is.
A test from one machine does not merely fail to answer the earlier questions. It answers several of them wrongly, in a reassuring direction. Your per-IP connection limit catches one source immediately. Bot detection flags a single odd client. Reputation filtering handles it. Every control in the first group appears to work, and you conclude you are covered.
Then a real distributed attack arrives from thousands of residential addresses and those same controls behave completely differently, because they key on source concentration and traffic realism, which is exactly what one machine cannot reproduce. The cheap check produces a false pass on the controls you most need to trust.
The same asymmetry applies to detection. A synthetic alert proves the routing works. It cannot tell you whether your dashboards show anything during an actual slow attack, or whether your logging captures it, because those failures depend on the traffic being real.
How to test whether your Slowloris defense works
You cannot infer this from a configuration review. Whether the pool actually holds depends on the interaction between your proxy, your web server, your application server and your connection limits under real concurrency, and that interaction is not visible in any single config file.
Obsidio runs Slowloris as a controlled, authorized simulation against infrastructure you own. Domain ownership is verified by DNS TXT record before any traffic flows, runs ramp up gradually, and any run can be aborted live. You choose the vector, the number of sockets held per worker, and the trickle interval, then watch what your stack does. Because load comes from 100,000+ globally distributed real devices rather than a handful of datacenter instances, a per-IP connection cap gets tested the way an actual distributed attack would test it.
Two things to watch during the run, both of which trip people up:
- Ignore the request-rate graph. For a slow attack, the rate figure counts connections opened, not throughput. It will look trivially low while the attack is succeeding. Watch concurrent held connections and peak connections instead. That is the metric that tracks whether the pool is filling.
- Measure from outside. Server-side metrics can look calm while real users are timing out in the queue. Confirm the effect with an independent request from outside your own network.
Plan for the detection questions too. Obsidio can report how your edge controls responded and how many connections were held, because that is visible from the outside. Whether your SIEM recorded anything and whether anyone was paged are answers only your own consoles hold, so have someone watching them during the window.
Strength is proven, not promised. Slowloris does not test your bandwidth. It tests whether anyone has checked your connection timeouts since the day they were set.
Where this fits in a testing programme
Slowloris belongs in the slow-attack category, alongside RUDY, which starves a request body rather than a header block. Both probe connection handling and timeout configuration. Neither is covered by an application-layer flood test, which is why a programme that only runs HTTP floods leaves this whole class unmeasured.
The ten simulation types fail in genuinely different places. An HTTP flood inverts almost everything above: the front defenses finally have something to act on, the damage travels as far as your database connection pool, and the detection problem flips from hearing nothing to drowning in alerts. Coverage across the categories is the point, not volume in any one of them.
For regulated institutions, the test also has to leave a record. Obsidio produces cryptographically attested, tamper-evident reports mapped to FINMA, DORA and NIS2, generated inside Trusted Execution Environments, so the result of a run is an artifact you can file rather than a screenshot you have to vouch for.
To scope an authorized Slowloris simulation against your own infrastructure, see the Obsidio platform or get in touch with the team. Independent, neutral, verifiable: resilience you can prove.
