Migrating from Apache to lighttpd

Configuration Basics

Lighttpd is a great option as a replacement for existing apache or apache2 systems.

Please start by reading about Configuration: File Syntax. The configuration syntax initially has the most significant learning curve, but you will see its flexibility once you familiarize yourself with it. One of the most powerful features of the lighttpd configuration is its conditionals. Lighttpd allows you to setup configuration options based on the client request. Conditionals can be nested, so you can create some very complex, yet effective configuration options. Conditionals are the key to many solutions which people encounter when migrating from apache/apache2.

You should familiarize yourself with regular expressions to truly appreciate the flexibility of lighttpd conditionals. You will use conditionals in your configuration file(s) for a multitude of things. Authentication, URL Rewriting, Virtual Hosting, and many more. All the variables you can use in conditionals are listed in Configuration: File Syntax along with further explanation about the match operators.

Q: How do I enable directory listings everywhere except for directory /download?

####### Enable dir listing for all directories
dir-listing.activate = "enable" 

####### If the URL is like ^/download/ then disable dir-listing
$HTTP["url"] =~ "^/download/" {
  dir-listing.activate = "disable" 

Q: How do I restrict the status page to be available only from the local network?
$HTTP["remoteip"] == "" {
  status.status-url = "/server-status" 

Q: How do I enable directory listing only if the host is and the url is under /download/?
Conditions can be nested:
$HTTP["host"] == "" {
  server.document-root = "/var/www/servers/" 
  $HTTP["url"] =~ "^/download/" {
    dir-listing.activate = "enable" 

For more conditional examples read:

Basic Options

Options +FollowSymLinks becomes server.follow-symlink = "enable"


mod_accesslog accesslog.format = "..." supports many of the same options as in Apache.
logfile rotation is also handled similarly to as in Apache, e.g.
  • logrotate as it is used in debian package
    If you don't use the debian package copy ./debian/lighttpd.logrotate to /etc/logrotate.d/
    logrotate will send lighttpd a SIGHUP when it is time to rotate the logs and lighttpd will reopen the logs accordingly.
  • cronolog
    With cronolog, you pipe the accesslog to a pipe and let a external program handle the logfile writing:
    accesslog.filename = "|/usr/sbin/cronolog /web/logs/%Y/%m/%d/access.log"


mod_rewrite can be more complex. The lighttpd implementation of how the rules are written is very different from that of Apache. lighttpd always matches on the full relative request-uri that is submitted by the user (and is not url-decoded).
  • In lighttpd 1.4.50 and later, ${qsa} can be used in the regex replacement to append the query-string from the request, similar to Apache's rewrite QSA flag.
  • In lighttpd versions earlier than lighttpd 1.4.50, we can emulate apache's QSA flag ("query string append"; see mod_rewrite in Apache) by matching on the query string and adding it back to the target. url.rewrite = ( "^/something/(\d+)(?:\?(.*))?" => "/index.php?bla=$1&$2" ) The important thing is (?:\?(.*))? which matches on the query string and is inserted in the target url using $2, the second capturing parentheses of the regex. ?: makes the surrounding parentheses non-capturing.

The following example is based on a problem from sent in by dbird@freenode.

RewriteEngine On
RewriteBase /instadir/
RewriteCond %{REQUEST_FILENAME}  -d
# Fix trailing slash problem
RewriteRule ^(.+[^/])$           $1/  [R,L]
# Do not try to treat the following resources as parameters to index.php
RewriteRule ^index\.php.*$       - [L]
RewriteRule ^dmoz\.css$          - [L]
RewriteRule ^admin[/]?.*$        - [L]
RewriteRule ^img[/]?.*$          - [L]
RewriteRule ^[/]{0,}(.*)$ index.php?area=browse&cat=$1 [QSA,L] 

These rewrites want to rewrite everything that is not the index.php, dmoz.css, admin-interface or something from the image-directory to a parameter of the index.php page. The base directory for this match is /instadir/. The first pattern below uses regex syntax called zero-width negative look-ahead assertion and is something from the advanced chapters of your regex book.
## for all URLs in /instadir/ that are not index.php, dmoz.css, admin or img, do ...
url.rewrite-once = ( "^/instadir/(?!index\.php|dmoz\.css|admin|img).*" => "$0", 
                     "^/instadir/([^?]*)(?:\?(.*))?" => "/instadir/index.php?area=browse&cat=$1&$2")

In lighttpd 1.4.50 and later, the above can be simplified to
## for all URLs in /instadir/ that are not index.php, dmoz.css, admin or img, do ...
url.rewrite-once = ( "^/instadir/(?!index\.php|dmoz\.css|admin|img)" => "",
                     "^/instadir/([^?]*)" => "/instadir/index.php?area=browse&cat=$1${qsa}")

If some combination of Apache rewrite rules can not be written in lighttpd mod_rewrite rules, then another option is lighttpd mod_magnet, which uses lua (a real programming language). Arbitrarily complex logic can be written in lua using lighttpd mod_magnet in lieu of mod_rewrite.


If you have 2 extensions assigned to the fastcgi handler in Apache like
AddType fastcgi-php .php .phtml
then you need to tell lighttpd mod_fastcgi to treat the extensions as the same: = ( ".phtml" => ".php" )
fastcgi.server = ( ".php" => (( "bin-path" => "/my/fastcgi-php", 
                                "socket" => "/path/to/php.socket" )),

If you need to use PHP and Python in the same setup, just add two extensions:
fastcgi.server = ( ".php" => (( "bin-path" => "/my/fastcgi-php", 
                                "socket" => "/path/to/php.socket" )),
                   ".py" => (( "host" => "", "port" => 3200 ))

For the above example, python was spawned externally and is a waiting for requests at port 3200, localhost.


How to setup a similar mechanism to the Apache's MultiViews option ?

Fortunately, Christian Hoffman wrote a lua script to do that. Here is how to use it :

server.modules = ( "mod_magnet", )
server.document-root = "/var/www" 
magnet.attract-physical-path-to = ( server.document-root + "/multiviews.lua" )

Another option is content-negotiation.lua

Directory !ForceType Migration

There are several PHP packages and tutorials that use the apache ForceType directive instead of mod_rewrite. They use it as a Search Engine friendly way to pass arguments as fake directories.

Let say you have this setup:
  • Your script is called news (note no php extension)
  • Your URLs look like /news/100/ (/news/_id of the news_)
  • Your script grabs $_GET!['PHP_SELF'] to determine the news id

and apache is configured with a Location or LocationMatch entry like this:

<Location /news>
   ForceType application/x-httpd-php
The easiest way to migrate this is:
  • First setup PHP with lighttpd and make sure .php extension is working
  • Rename filename news to news.php
  • Use a lighttpd rewrite to replace the Forcetype entry

In lighttpd configuration file:

url.rewrite-once = ("^/news/.*" => "/news.php")

If your script is still not working, try to replace
$_GET!["PHP_SELF"] with $_GET!["REQUEST_URI"] You can find it usually at the top of the script.
PHP does not register PHP_SELF correctly when running in FastCGI.

Pretty URLs (or '.php' script auto-detection)

I had a problem after migrating in duplicating my old Apache1.x + PHP setup had '''''' happily running ''script.php_ with arguments _/blah''. This is really useful for easily making pretty URLs.

I couldn't see an easy solution to replicate this behaviour, or any entries in the docs here, so I decided to share my findings. Essentially there are two ways to go about replicating this in a semi-generic way so that you don't have to recode / rename your actual scripts.

If you were happy to rename you could probably make things work using lighttpd's forcetype support.

mod_rewrite solution

This is the one I picked because it's faster and seemed neater. Basically I added

include "mod_rewrite.conf" 

... and then created that file with the contents ...

url.rewrite-repeat = ( "/script/(.*)" => "/script.php/$1" )

I guess you can also do this in a semi-generic way by naming each script:

url.rewrite-repeat = ( "/(script|script2|script3)/(.*)" => "/$1.php/$2" )

404 handler solution

This is probably minutely slower but may be a better idea if you have more complex (ie: not pure regex) considerations in your URL rewriting. You can also replicate the regex above using PHP preg_match(). Just add:

server.error-handler-404 = "/404.php" 

... then write your handler in that file.


$HTTP["host"] == "" {
            alias.url  = ( "/bin/" => "/path/to/my/bin/" )
            $HTTP["url"] =~ "^/bin" {
                    cgi.assign = ( ".pl" => "/usr/bin/perl" )

Another solution could be to use mod_alias with:

cgi.assign = ( ".py" => "/usr/bin/python" )
alias.url = (
    "/chrome" => var.basedir + "/",
    "" => var.basedir + "/" 

The lack of an ending '/' will work as a wildcard.

Inside this script, get the environment variable 'REQUEST_URI' to figure out the 'pretty' ending string.

In python this would be: os.environ['REQUEST_URI'] (omitting import), in php: $_SERVER['REQUEST_URI'].

.htaccess-like functionality

.htaccess-like functionality
Apache .htaccess alternatives

Arbitrarily complex logic

If lighttpd modules do not natively support a specific set of logic, there is another option: lighttpd mod_magnet.
Arbitrarily complex logic can be written into a lua scripts and run with mod_magnet.

Updated by gstrauss over 1 year ago · 51 revisions