Moving back to Lighttpd

There are some FreeBSD machines in our infrastructure which run NGINX. After the recent announcement on the F5 purchase of NGINX we decided to move back to Lighttpd.

We have not seen a lot of open source projects doing well after the parent company got acquired. We used Lighttpd in the past, before the project stalled, doesn’t seem to be the case anymore. We decided to check it out again.

The configuration discussed here is roughly what we used NGINX for.

A lot of the options within Lighttpd are enabled by using modules. These are the modules we have enabled on all our Lighttpd servers.

server.modules = (
  "mod_auth",
  "mod_expire",
  "mod_compress",
  "mod_rewrite",
  "mod_redirect",
  "mod_alias",
  "mod_access",
  "mod_setenv",
  "mod_evhost",
  "mod_fastcgi",
  "mod_accesslog",
  "mod_openssl"
)

To specify which IP and port Lighttpd listens on is defined in a couple of different ways. For IPv4 server.port and server.bind are used. For IPv6 you have to use $SERVER[“socket”]. The same is true for the SSL config.

server.port = "80"
server.bind = "0.0.0.0"
$SERVER["socket"] == "[::]:80" { }
$SERVER["socket"] == "[::]:443" { }
$SERVER["socket"] == ":443" {
  ssl.engine = "enable"
  ssl.pemfile = "/usr/local/etc/ssl/certs/example.com/combined.pem"
  ssl.ca-file = "/usr/local/etc/ssl/certs/example.com/chain.pem"
  ssl.cipher-list = ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-
CHACHA20-POLY1305:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384"
  ssl.dh-file = "/usr/local/etc/ssl/certs/dhparam.pem"
  ssl.ec-curve = "secp384r1"
  setenv.add-response-header = (    "Strict-Transport-Security" => "max-age=31536000; includeSubdomains",
    "X-Frame-Options" => "SAMEORIGIN",
    "X-XSS-Protection" => "1; mode=block",
    "X-Content-Type-Options" => "nosniff",
    "Referrer-Policy" => "no-referrer",
    "Feature-Policy" =>  "geolocation none; midi none; notifications none; push none; sync-xhr none; microphone none; c
amera none; magnetometer none; gyroscope none; speaker none; vibrate none; fullscreen self; payment none; usb none;" 
  )
}

Lighttpd requires a PEM certificate. Which you can easily create with: # cat domain.key domain.crt > combined.pem

You can create the dhparam.pem file with: # openssl dhparam -out dhparam.pem 4096

These are the global settings we are using on FreeBSD related to the server settings.

server.username = "www"
server.groupname = "www"
server.pid-file = "/var/run/lighttpd.pid"
server.event-handler = "freebsd-kqueue"
server.stat-cache-engine = "disable"
server.max-write-idle = 720
server.tag = "unknown"
server.document-root = "/usr/local/www/default/"
server.error-handler-404 = "/404.html"
accesslog.filename = "/usr/local/www/logs/lighttpd.access.log"
server.errorlog = "/usr/local/www/logs/lighttpd.error.log"
server.dir-listing = "disable"

Some global settings which apply to all the websites served by Lighttpd.

index-file.names = ("index.php", "index.html", "index.htm")
url.access-deny = ("~", ".inc", ".sh", "sql", ".htaccess")
static-file.exclude-extensions = (".php", ".pl", ".fcgi")

Alias for Let's Encrypt.

alias.url += ("/.well-known/acme-challenge/" => "/usr/local/www/acme/")

Enable compression for certain filetypes.

compress.cache-dir = "/tmp/lighttpdcompress/"
compress.filetype = ("text/plain", "text/css", "text/xml", "text/javascript")

When authentication is needed you can specify this as below. Different backends are supported.

auth.backend = "htpasswd"
auth.backend.htpasswd.userfile = "/usr/local/etc/lighttpd/htpasswd"

General Expire and Cache-Control headers for certain filetypes.

$HTTP["url"] =~ "\.(js|css|png|jpg|jpeg|gif|ico)$" {
  expire.url = ( "" => "access plus 1 months" )
}

When you are running Wordpress sites you might want to deny access to certain urls.

$HTTP["url"] =~ "/(?:uploads|files|wp-content|wp-includes).*\.(php|phps|txt|md|exe)$" {
  url.access-deny = ("")
}
$HTTP["url"] =~ "/(wp-config|xmlrpc)\.php$" {
  url.access-deny = ("")
}

Define for which host and url the authentication is needed.

$HTTP["host"] =~ "www1.example.com" {
  auth.require = ( "/admin/" => (
    "method" => "basic",
    "realm" => "Restricted",
    "require" => "valid-user" )
  )
}

Redirect certain hosts from http to https.

$HTTP["host"] =~ "(www\.)?example.com" {
  url.redirect = ("^/(.*)" => "https://www.example.com/$1")
}

There is a module available which helps to assign the correct server.document-root for virtual hosts. This can be done with mod_evhost and we are using the following pattern:

$HTTP["host"] =~ "^(www.)?[^.]+\.[^.]+$" {
  evhost.path-pattern = "/usr/local/www/www.%2.%1/"
}

To be able to use pretty urls with Wordpress you can use the following mod_rewrite rules.

url.rewrite = (
  "^/(wp-.+).*/?" => "$0",
  "^/(.*)\.(.+)$" => "$0",
  "^/(.+)/?$" => "/index.php/$1"
)

The final piece of the puzzle for when you are using PHP-FPM the following config can be used.

fastcgi.server = ( ".php" =>
  ( "localhost" =>
    (
      "host" => "127.0.0.1",
      "port" => 9000
    )
  )
)

The complete config can be found in our Git Repository