Slowloris vs Apache 2.2: What You Can Actually Tune
In the middle of June a small denial of service tool called Slowloris started making the rounds, and by June 17 someone had already posted it to the Apache users list. What makes it interesting isn’t raw power. It doesn’t flood your pipe. It opens a pile of connections to your web server, starts sending an HTTP request on each one, and then just never finishes. If you run a typical LAMP box on Apache 2.2 with the prefork MPM, that turns out to be enough to make the site stop answering, from a single machine, with very little traffic.
Here’s why that works, what Apache’s own settings can do about it, and what the httpd developers are saying on the public lists. No attack walkthrough, just the mechanism and the defense.
Slots, not bandwidth
Apache 2.2 on Unix handles connections with a fixed pool of workers. With prefork, every connection gets its own child process. With worker, it gets a thread inside a child. Either way there’s a ceiling, and that ceiling is MaxClients. The docs describe it as the limit on simultaneous requests that will be served, with anything over the limit queued (up to the ListenBacklog) until a worker frees up. For prefork the default is 256 processes, and raising it means raising ServerLimit too. For worker the default works out to 16 processes times 25 threads.
Now think about what a worker does while a request is arriving. An HTTP request is a request line followed by headers, and the end of the headers is marked by an empty line. Until that empty line shows up, Apache can’t hand the request to PHP or anything else, so the worker sits in a read, waiting. A client that sends a header line, pauses, sends another, pauses again, and never sends the blank line keeps that worker parked. Do that on a few hundred connections and every slot is busy “reading a request” while real visitors wait in the listen queue until they give up.
None of this is a bug in the usual sense. It’s what a worker per connection design does when clients are slow. A visitor on a terrible connection holds a slot the same way, just not on purpose and not by the hundred.
Why Timeout doesn’t save you
Surely Apache times these out? It does, just not the way most of us assumed. TimeOut is how long Apache waits on I/O, and in 2.2 it defaults to 300 seconds. The 2.2 docs describe it as covering three things, and the first one listed is the total amount of time it takes to receive a GET request.
That reading didn’t survive contact with testing. On the users list, in a thread about using Timeout against DoS attacks, someone set Timeout to 3, sent headers with a two second pause between each one, and found the request was not dropped after 3 seconds. William Rowe replied that Timeout handling could be refactored to behave as advertised, and that there is a discrepancy with the documentation. Joe Orton put it more precisely on the dev list: what he wants is timeouts at the granularity of a whole request read, rather than per I/O operation. So in practice the clock resets whenever a bit of data arrives, and a client that trickles bytes in faster than your Timeout never trips it.
The knobs you actually have
Apache’s development docs have a new denial of service section in the security tips page that hasn’t reached the 2.2 copy yet. It’s the best official checklist I’ve found, and it’s upfront that you can’t prevent these attacks entirely, only reduce the damage.
Lowering Timeout is the first suggestion, and the page says a few seconds may be appropriate on sites under attack. The catch it spells out is that Timeout is used for several different things, so a low value causes trouble for long running CGI scripts. It also governs how long Apache waits on a slow client while sending a response, so very aggressive values can hurt real users on bad links. Given the per operation behavior above, a lower Timeout only forces a slow client to send something more often. Worth doing, but not the fix.
KeepAliveTimeout is the second. It’s how long Apache waits for the next request on a persistent connection, default 5 seconds, and the docs warn that the higher it is, the more server processes are kept occupied waiting on idle clients. It doesn’t govern a request that’s still arriving (once a request is received, Timeout applies), but trimming it frees slots held by idle visitors, which buys headroom. Some sites turn KeepAlive off entirely, as the security tips mention, but the KeepAlive docs say persistent connections have in some cases cut latency almost in half for pages with many images, so I’d shorten the timeout first.
Something like this is a reasonable starting point for a small prefork site, adjusted to your own traffic:
Timeout 30
KeepAlive On
KeepAliveTimeout 3
MaxClients is the third knob, and it’s the one with a real tradeoff. A higher ceiling means more connections to fill. Nick Kew made that point on the users list when someone posted a config with MaxClients 150: that’s only 150 connections to tie up the server. The problem on a LAMP box is memory. Each prefork child with mod_php loaded is heavy, and the 2.2 performance tuning guide tells you to size MaxClients by dividing available memory by your average Apache process size so the server never swaps. The security tips note that a threaded MPM may let you handle more simultaneous connections, but the PHP manual’s installation FAQ recommends against a threaded MPM with mod_php in production and points you to FastCGI if you go that way.
The security tips page also says that often the most effective anti DoS tool will be a firewall or other OS level configuration, since most firewalls can cap simultaneous connections from one IP address. One attacking machine needs lots of connections from one address, so a per IP cap is exactly the right shape. The page notes it’s no help against a distributed attack, and a big office behind one NAT address can trip a tight cap, so leave some room.
What about the event MPM
Apache 2.2 does ship an event MPM, and people on the lists are asking if it’s the answer. Its docs say it uses a dedicated thread to handle listening sockets and all sockets in keepalive state, so an idle persistent connection no longer pins a worker. But the same page flags it as experimental and says it only works with epoll or kqueue. It’s also threaded, so the PHP caveat applies, and Rowe pointed out on the users list that event doesn’t help with a slowly sent request body. For most LAMP boxes it’s an experiment, not a fix.
What the httpd developers are saying
I haven’t seen a formal advisory about Slowloris from the Apache project. The discussion is happening in the open. Andreas Krennmair started a dev list thread saying he’d contacted the Apache security team and was referred to the list to discuss it as a non private issue, and he posted a proof of concept patch against 2.2.11 prefork that shortens timeouts as the server gets busier.
The replies are worth reading. Paul Querna said mitigation is the wrong approach, that the architecture is the real problem, and that the async input rewrite on trunk is unfinished because the people who worked on it ran out of time. Joe Orton disagreed with the framing: any server has a maximum number of concurrent connections, so one client allowed to grab all of them will deny service to everyone else, and that has to be handled at the network or firewall level (on Linux, he noted, with a single iptables rule). He also pointed out that the attack is far from invisible if you watch how many responses per minute your server completes. Guenter Knauf suggested limiting simultaneous connections per IP, and Nick Kew floated the idea of a small module to do just that. Rowe described a truly asynchronous server as a major, module breaking 3.0 change that isn’t right around the corner.
So there’s no patch to wait for, and the advice from the project’s own people lines up with the docs.
How event driven servers differ
Event driven servers work differently. nginx’s own feature list describes one master process and several worker processes using kqueue or epoll, and says 10,000 inactive HTTP keepalive connections take about 2.5M of memory. Next to a prefork child per connection, that’s a very different cost for an idle connection. It’s a figure about idle keepalives, not a promise about slow requests, and switching servers is a much bigger change than editing httpd.conf, but it’s worth testing if you were already considering it.
The takeaway
Slowloris doesn’t break Apache so much as expose how prefork and worker spend their slots. On a 2.2 LAMP box this week, I’d add a per IP connection limit at the firewall, bring Timeout well below 300, shorten KeepAliveTimeout, and set MaxClients to what the machine’s memory can actually support. None of that makes you immune, but together they turn a one laptop outage into something that takes real effort, and all of it can be rolled back in an afternoon.