markround.com

DevOps, Sound Engineering and other things of interest...

Migrating From Apache to Lighttpd

| Comments

In my role as a sysadmin, the bulk of the Unix systems I administer are web servers, running the now standard open-source stack of Apache, MySQL and PHP (note that whatever my personal misgivings may be about those elements, they are pretty much the standard now and what’s been mandated at work). If you’re using PHP on Unix, it’s pretty much taken for granted that you’ll be running it through Apache via mod_php. In fact, it almost goes without syaing that if you’re doing any kind of webserving on Unix at all, you’ll most likely be using Apache. It’s a setup that has perfomed well in each instance I’ve deployed it – from small personal sites and development systems, to the large high-traffic sites I’m responsible for at work. It’s free, robust, and above all well documented. So why am I now seriously considering ditching Apache ? One word :LigHTTPd. I’d been hearing a lot recently about this webserver, including the usual foaming at the mouth advocacy from the Ruby-On-Rails crowd – but more interestingly and certainly more pertinant to my circumstances, were the glowing reports of people running PHP through it. So, over the last few days I’ve been experimenting with it, culminating with moving all sites on this webserver (including my blog) over to Lighttpd. To put it mildly, I’ve been blown away. It’s been a long time since I was this impressed with a piece of software, let alone something as apparently mundane as a webserver.

Inital impressions

My first impressions were pretty favourable. Nice clean website, easy to get at the downloads, and it compiled first time with a minimum of hassle. All I found I had to do was build pcre separately, and then set the appropriate environment variables (CFLAGS, LDFLAGS etc.) to get it to pick up on that. I then started poking around the configuration syntax and got a bare-minimum server up and running in a matter of minutes. Now, I was a bit wary of having to learn a new configuration syntax – I’ve been running Apache for so long it’s become almost totally ingrained in me, and I thought I’d hate having to learn a new syntax. How wrong I was – the config file structure is one of the best I’ve ever encountered. It feels like it’s been heavily influenced by scripting languages, and as such felt right at home in a Unix environment. An example may be in order to give you an idea. Take for instance, the age-old "restrict a URL to a certain IP address" problem. In Apache, you’d do this with a Location or Directory block, set the appropriate allow and deny order, and then Deny from all, and only allow to the specific IP address. All well and good, but how many times have you mixed up the "Order" configuration ? Despite the thousands of times I’ve set this up, I still find myself pausing before I commit the changes and manually running through the rules in my head, taking into account the default behaviour. As you’re probably aware, this is how you’d restrict access to /server-status in Apache :

<Location /server-status>
SetHandler server-status
Order deny,allow
Deny from all
Allow from 1.2.3.4
</Location>

And this is how you’d do it in Lighttpd :

$HTTP["remoteip"] != "1.2.3.4" {
url.access-deny = ("/server-status")
}

Great, eh ? There are whole bunch of conditionals you can embed within your configuration file, including nested statements and regular expression matching. In fact, that’s how vhost configuration is managed:

$HTTP["host"] =~ "^(www\.)?foo.com" {
...
}

And you just plonk your vhost settings in between the curly braces. Or how about using variables inside your configuration ? Take a look at the following :

$HTTP["host"] == "www.example.com" {
var.basedir = "/sites/www.example.com/"
server.document-root = basedir + "html/"
accesslog.filename = basedir + "logs/access.log"
}

It gets better, too – want to enable SSL support for your server ? Forget all the complex Apache directives, in Lighttpd it’s reduced to a few lines :

$SERVER["socket"] == "1.2.3.4:443" {
ssl.engine="enable"
ssl.pemfile="/usr/local/lighttpd/etc/certificate.pem"
}
Notice what’s so cool about that – it also binds to the specified address and port – otherwise, what would the point be in checking it ? This approach of "doing the right thing" extends to all parts of Lighttpd. Take the gzip implementation, for instance. It’s widely acknowledged that gzip’ing your content will dramatically lower your bandwidth, however it can also add a fair amount of overhead to an already busy server as it has to compress each file it sends out. Lighttpd takes a great common-sense approach, in that it caches compressed static content. If the file hasn’t been updated since it was compressed, there’s no point in running the expensive gzip compression again – it just serves the cached version.

Features

Fortunately, Lighttpd also supports most of what are on my list of essential features for a webserver, namely access and authentication control (using a variety of backends, including LDAP – more on that later), and rewrites. Syntax is obviously a little different to Apache, but that’s no bad thing. In fact, after only a few hours hacking, I managed to get a variety of applications running flawlessly on Lighttpd – including the Serendipity blog this site runs on, the Wikka wikki software, and the FogBugz bug tracking system. Yes, the same system I ranted about in an earlier post ;-) I’ll post my configurations here if any one is interested. There are also a whole raft of additional modules and plugins available – despite the documentation being a little sparse in places and missing some useful examples, there’s always the wiki, forums and mailing lists for interacting with other users.
Performance
Now we come to the real crux of the matter. I haven’t yet run any serious performance benchmarks, but in just casually observing Lighttpd and Apache 2.x running on the same system, the difference is night and day. Lighttpd beats Apache/mod_php hands down – in most cases running around twice as fast on dynamic content, and easily 4 times as fast on static content. It even makes FogBugz useable! What makes all this the more impressive is that it doesn’t make use of an in-process server module such as mod_php, instead it uses the FastCGI API. In theory, having a separate PHP process which the webserver communicates with over a socket should be slower than mod_php, but this doesn’t seem to be the case at all with Lighttpd. You also gain big improvements on flexibility and stability, plus you can use all your usual PHP modules including opcode caches such as APC and even the Zend platform. Memory footprint is reduced dramatically as well. Lighttpd takes a different approach to handling concurrency than forking webservers such as Apache 1.3 or multithreaded webservers such as Apache 2.x or iPlanet. Instead, it utilises a single-process, single-threaded approach that uses a select/poll queue to process events which utilises features of the underlying operating system for maximum performance. On Solaris for instance, it utilises /dev/poll whereas on FreeBSD it can make use of the kqueue facility. Despite being superb for static files, this approach would obviously suck for dynamic requests, so instead it passes these off to a number of FastCGI backends (such as PHP, in this case). This means it can then get on with processing the next request, sending content back when the script has finished executing. This means you only need to have as many php processes running as you need to service dynamic requests – in Apache, even though you may just be serving up a small static file, you still have mod_php loaded with all it’s extensions, which on a typical system can eat up as much as 40Mb per process. You can also spawn multiple lighttpd "workers" which can then take advantage of multi processor systems; however it appears for dynamic sites the best approach is to scale out your backend FastCGI processes. In fact, this opens up a whole new set of scaling opportunities. In the Apache/mod_php world, if your web server is becoming bogged down with dynamic requests, your only real option is to either scale the hardware, add additional webservers and load-balance requests between them, or try and split out your static requests such as images, so that they go to a different server (or whack a caching reverse proxy in front). With Lighttpd, you can have a screaming fast webserver one one system, and have it hand off dynamic requests to a backend FastCGI server doing nothing but processing dynamic requests. If you need more processing power, simply add another FastCGI server and tell lighttpd to distribute the load amongst them. The manual gives an example of this, in a typical sensible lighttpd manner :
fastcgi.server = ( ".php" => 
(( "host" => "10.0.0.2", "port" => 1030 ),
( "host" => "10.0.0.3", "port" => 1030 ))
)

Final thoughts
By now, I’m totally sold. I’ll be running some serious benchmarks against Apache 1.3.x and 2.x in the coming weeks, but I highly recommend that if you even have a passing interest in setting up webservers, you download this superb piece of software and give it a try. The only sticking point I’ve found is that some rewrite rules can’t be duplicated exactly from Apache, so you have to re-work them using conditionals and in some cases, some serious regex voodoo. But that’s only to be expected, really. It’s light yet full featured, the config file is simple but can be made to do some amazingly complex things, it’s blazingly fast, runs brilliantly on Solaris… in fact I can’t think of anything so far that bugs me about it. In short, Lighttpd rocks.

Comments