As I said in my previous post, I went with lighttpd over Apache for use in my slice. It is quite a bit more memory efficient, needing only about 2 MB of RAM when running on my server. However, it more than does the job I ask it to do.

Because I had not worked too much with it in the past, I thought I would write up a few of the gotchas and things that tripped me up. Being "not Apache", there are a few things worth pointing out.

Basic Performance Tuning

I spend way too much time worrying about such things, but then again, that is why we all hack at Linux and stuff in the first place. One big step you can take on Linux is to use the epoll event handler:

## set the event-handler (read the performance section in the manual)
server.event-handler = "linux-sysepoll"

Not that it matters much for my little corner of the internet, but epoll() scales a whole lot better than poll() so it makes a lot of sense to enable it. Lighttpd also uses the sendfile() syscall by default, so that can give some performance benefits. For more on performance stuff, see the good writeup in the lighttpd wiki.

Django Integration

Although it is a supported setup, it took quite a bit of searching and looking around to find all the details I needed to get things running well.

Here is the majority of my vhost setup from lighttpd.conf:

$HTTP["host"] == "www.toofishes.net" {
  fastcgi.server = ( "/django.fcgi" =>
                     ( "django" =>
                       (
                         "socket" => "/var/lib/lighttpd/django-fastcgi.socket",
                         "bin-path" => "/path/to/startfcgi.sh",
                         "check-local" => "disable",
                         "max-procs" => 1,
                       )
                     )
                   )
  alias.url += ( "/media/" => "/path/to/django/media/",
                 "/admin_media/" => "/usr/lib/python2.6/site-packages/django/contrib/admin/media/" )
  url.rewrite-once = ( "^(/media/.*)$" => "$1",
                       "^(/admin_media/.*)$" => "$1",
                       "^(/robots.txt)$" => "$1",
                       "^(/.*)$" => "/django.fcgi$1" )
}

The biggest thing of note here is that we rewrite everything (except static media) to /django.fcgi, and that is simply the fastcgi URL managed by lighttpd. That config is relatively standard, except nowhere in the Django documentation do they say a wrapper script to start the fastcgi process might be a good idea. The contents of that script are below:

#!/bin/sh
app_path='/path/to/django/app'
p='/var/run/lighttpd/django-fastcgi.pid'
cd "$app_path"
if [ -f $p ]; then
    kill $(cat -- $p)
    rm -f -- $p
fi

exec /usr/bin/env \
    PYTHONPATH="$app_path/.." python \
    manage.py runfcgi \
    daemonize=false \
    method=prefork \
    maxspare=2 \
    pidfile="$p"

This script's sole purpose is to start up the listening end of the fastcgi chain. I went with the prefork method (as opposed to threaded), but in order to keep memory usage down in my 256 MB slice, I set maxspare to 2 rather than the default of 5.

A final note- when running with lighttpd, you need to set the following in your Django application settings.py:

# Force lighttpd to get the {% url %} tags right
FORCE_SCRIPT_NAME = ''

Be sure to set it to the empty string and not '/', which appears to work but will break several key parts of the admin interface. The Django docs touch on this issue, but this post made the issue a lot clearer.