Features Lighttpd Setup Lead image: Photo by Brett Jordan on Unsplash
Photo by Brett Jordan on Unsplash
 

Setting up the lightweight Lighttpd web server

Fast Delivery

The long-established Lighttpd web server is lean and fast and can be set up quickly thanks to its simple configuration. By Tim Schürmann

In terms of functionality, the small Lighttpd [1] web server need not shy away from comparison with more prominent competitors. Among other features, it supports virtual host strategies and can change its configuration to reflect incoming requests. If necessary, further functions can be added with the use of modules. For example, access to individual websites can be restricted, connections can be secured by TLS, and scripting languages can be integrated over the FastCGI interface, which also has a built-in load balancer that distributes incoming requests to multiple PHP servers.

By the way, the web server's name is pronounced "Lighty"; some websites even use this phonetic spelling as a synonym of the official name. Lighttpd supports HTTP 1.0/1.1 and encrypted connections over HTTPS. Versions 1.4.56 or later support the newer HTTP/2. Because of its small footprint, Lighttpd is often used on smaller or less powerful systems. For example, it is the engine for the popular Pi-hole [2] network filter and ad blocker. The easy-to-understand configuration file allows lightning-fast setup.

Quick Installation

Most distributions have Lighttpd in their repositories. On Ubuntu, you can install it with the command:

$ sudo apt install lighttpd lighttpd-doc

Some distributions keep only rarely needed modules in separate packages with names that often start with lighttpd-. In Ubuntu's case, lighttpd-webdav retrofits a WebDAV interface, for example. For your first steps with the web server, though, you will not normally need these modules.

To build Lighttpd from the source code, you need at least a C compiler, Make, the pcre-config tool, and the developer packages for Zlib and Bzip2. On Ubuntu, the command

$ sudo apt install build-essential libpcre3-dev zlib1g-dev libbz2-dev

retrieves everything you need. The numerous dependencies for all optional Lighttpd modules are listed in the Lighttpd wiki [3]. You can compile the Lighttpd source code from the project website with the familiar three-step process:

$ configure
$ make
$ sudo make install

In the doc/systemd/ source code directory, you will find matching configuration files for systemd.

Quick Setup

All of the settings for the web server are grouped in the lighttpd.conf file, which is located by default in the /etc/lighttpd/ directory (see also the "File Split" box). As a starting point for your own lighttpd.conf, I would recommend the simple configuration shown in Listing 1. On each line, you have the name of a setting on the left, followed by the associated value on the right, separated by an equals sign. Lighttpd ignores lines that start with the hash symbol (#).

Listing 1: Simple Lighttpd Config

var.dir = "/var/www"
# Define basic settings:
server.document-root = var.dir + "/html"
server.port = 80
server.username = "www-data"
server.groupname = "www-data"
server.errorlog = "/var/log/lighttpd/error.log"
mimetype.assign = (
  ".html" => "text/html",
  ".txt" => "text/plain",
  ".jpg" => "image/jpeg",
  ".png" => "image/png"
)
static-file.exclude-extensions = ( ".fcgi", ".php", "~", ".inc" )
index-file.names = ( "index.html" )

In more complex configurations, some domain names, directories, or URL components appear in several places. To save typing, you should define variables for such information to use as placeholders later. Listing 1 makes use of this possibility right at the beginning. The /var/www directory is assigned to the var.dir variable.

Another advantage of variables becomes apparent if the directory changes at some point: All you need to do is adjust the var.dir variable. The variable name must begin with var. but can be freely chosen otherwise. The only exceptions are var.PID with the process ID of the running Lighttpd and var.CWD with the current working directory.

The server.document-root setting is followed by the directory where all the web pages are stored (the document root). Note that the var.dir variable is used here with /html appended to its value. In general, the + operator allows any two strings to be concatenated. In the example, this specifies a folder named /var/www/html.

The web server later listens on the port that follows server.port. To access port 80, Lighttpd must be launched with root privileges. To avoid vulnerabilities, the server in Listing 1 automatically switches to the www-data account in a group of the same name (i.e., to the user account provided by Debian and Ubuntu for this purpose).

Food for Talk

The server.errorlog parameter specifies the text file in which Lighttpd logs its messages and will also contain all the warnings and notices. Adding server.errorlog-use-syslog = "enable" would tell the web server to write these messages to the syslog. Finally, the additional line server.use-ipv6 = "enable" would enable support for IPv6.

In its response, the web server sends along the MIME type of the transmitted data, such as text/html for an HTML document. To assign the appropriate MIME type to individual file extensions, you use mimetype.assign. The setting demonstrates the use of a list: The assignments are each placed between parentheses as a comma-separated list. In Listing 1, the individual values of the list are on a separate line, just for the sake of clarity, but the formatting is not significant.

PHP scripts should be executed by the web server and not delivered to the browser. The extensions of files to be ignored are listed in static-file.exclude-extensions. When a browser accesses a directory, Lighttpd looks for one of the files listed after index-file.names and returns it. If none of these index files exists, Lighttpd returns a 403 error message. Unlike many competitors, Lighttpd prohibits its clients from accessing directories directly – this kind of access has to be permitted explicitly with the dirlisting module.

Supplying the Configuration

As an experiment, save your lighttpd.conf file if it already exists, and then create a new configuration file with the contents from Listing 1. Type

lighttpd -t -f /etc/lighttpd/lighttpd.conf

to first check for syntax errors (Figure 1). A typo like server.prot=80 would not be detected. You should therefore still check the error log after reloading the configuration. Figure 2 shows a configuration test with the parameter -D, which outputs messages to the terminal.

The -t parameter tells Lighttpd to check syntax.
Figure 1: The -t parameter tells Lighttpd to check syntax.
The -D switch keeps Lighttpd in the foreground and outputs messages to the terminal, which would be useful for a quick test of a new configuration.
Figure 2: The -D switch keeps Lighttpd in the foreground and outputs messages to the terminal, which would be useful for a quick test of a new configuration.

The new settings are applied by the web server as soon as it receives the SIGUSR1 signal. If you have a distribution that uses systemd, use:

sudo systemctl reload lighttpd.service

Otherwise the manual approach is to run

sudo kill -n SIGUSR1 <PID>

To discover the <PID>, run:

ps -A | grep lighttpd

Table 1 lists the signals that Lighttpd understands. If the web server is not yet running, typing either of the lines,

sudo systemctl start lighttpd.service
sudo lighttpd -f /etc/lighttpd/lighttpd.conf

will enable it at once.

Tabelle 1: Signals Supported by Lighttpd

Signal

Response

SIGTERM

Terminates Lighttpd immediately; existing connections are interrupted

SIGINT

Lighttpd responds to all current requests and then terminates

SIGUSR1

Lighttpd responds to all current requests and then reloads its configuration

SIGHUP

Lighttpd reopens its logfiles but does not reload the configuration

Flexible Condition

Imagine you want Lighttpd always to return pages from /var/www/html/blog when a browser requests the blog from blog.example.com. You can manage this in the configuration file with conditionals. The lines

$HTTP["host"] == "blog.example.org" {
  server.document-root = var.dir + "/html/blog"
}

test whether the requested hostname is identical to blog.example.com.

The $HTTP["host"] element stands for the requested host name; all data available as an alternative is summarized in Table 2. If the condition is true, Lighttpd evaluates all the settings in the brackets. The example defines /var/www/html/blog/ as the document root folder. All settings between the curly braces thus only apply to blog.example.org. In this way, completely different configurations can be stored for different domains and, in turn, virtual hosts can be implemented.

Tabelle 2: Data in Conditionals

Field

Meaning

$REQUEST_HEADER["..."]

The information specified in the quotes from the request header (e.g., $REQUEST_HEADER["User-Agent"] refers to the user agent)

$HTTP["request-method"]

Request method

$HTTP["scheme"]

Schema of incoming connection, either http or https

$HTTP["host"]

Hostname

$HTTP["url"]

Complete URL path without the domain name and query strings

$HTTP["querystring"]

Query string (everything after the ? in the URL)

$HTTP["remoteip"]

Remote IP or remote network; only works with IPv4

$SERVER["socket"]

Socket; only works with the == operator; additionally, the value must have the format <IP>:<Port>.

Instead of ==, a != tests for inequality. Additionally, more complex rules can be formulated with regular expressions. Lighttpd evaluates settings in the curly brackets if the regular expression to the right of =~ is true or the expression to the right of !~ is false. For example,

$HTTP["url"] =~ "^/blog/"

would check to see whether the URL requested by the browser starts with /blog/. Lighttpd also supports conditionals. Consequently, a conditional can contain other conditions between the curly braces.

Communication Secret

Most of Lighttpd's functions are provided by modules that can be activated as needed. Encrypted connections over TLS are provided by the mod_openssl module,

server.modules = ("mod_openssl")
$SERVER["socket"] == ":443" {
  ssl.engine = "enable"
  ssl.pemfile = "/etc/lighttpd/server.pem"
}

which in turn relies on OpenSSL. In Lighttpd 1.4.56, four more modules joined the mix, handling encryption with mbed TLS (mod_mbedtls), wolfSSL (mod_wolfssl), GnuTLS (mod_gnutls), and NSS (mod_nss). However, they are still considered experimental.

To use the SSL module, you must first load it in lighttpd.conf:

server.modules = ( "mod_openssl" )

The settings for the module end up back in the central configuration file. In the case of mod_openssl, the mod_openssl module lines shown previously are all you need: The conditional checks whether the request has come in through port 443. In this case, ssl.engine enables encryption with the certificate specified by ssl.pemfile.

You can generate a suitable self-signed certificate with OpenSSL:

$ openssl req -new -x509 -keyout /etc/lighttpd/server.pem -out /etc/lighttpd/server.pem-days 365 -nodes

Lighttpd also supports the Let's Encrypt project. The somewhat more complex configuration required for this is explained in detail on a wiki page [4].

Account Control

The mod_auth module takes care of authentication. To activate it, either add it to the list after server.modules or add it later to lighttpd.conf with the += operator:

server.modules += ("mod_auth", "mod_authn_file")

Two modules are needed for access control: mod_auth fields a username and password and then asks a backend whether the user has sufficient access rights. The backends in turn provide other modules, one of which is mod_authn_file. This module provides several backends, all of which read user data from text files. Other available backends use PAM or an LDAP server instead.

Which backend mod_auth needs to use is defined by a setting in lighttpd.conf. Listing 2 uses the plain backend, which expects the passwords in plain text in a simple text file. auth.backend.plain.userfile tells the appropriate module to fetch the user data from the /etc/lighttpd/user.txt file. It contains the user names and passwords on each line in a <User>:<Password> format. Finally, auth.require specifies that access to the /blog URL is restricted, the password is in the clear ("method" => "basic"), and any authorized user is granted access ("require" => "valid-user").

Listing 2: Determining Backend

auth.backend = "plain"
auth.backend.plain.userfile = "/etc/lighttpd/user.txt"
auth.require = ( "/blog" => ("method" => "basic", "realm" => "application", "require" => "valid-user") )

The example shows an easily set up but relatively insecure form of authentication. Lighttpd and its modules do not support the .htaccess files known from Apache. As a consequence, a web application that builds on that basis could be vulnerable on Lighttpd.

Distribution Box

Lighttpd prefers to integrate scripting languages over the FastCGI interface, which the mod_fastcgi module installs retroactively. At the same time, it includes a load balancer that distributes the load across multiple FastCGI servers. Suitable settings for lighttpd.conf are shown in the example in Listing 3; the module distributes the requests evenly among the servers by a round-robin method (line 2).

The settings in the fastcgi.server module reveal to which FastCGI servers Lighttpd sends a script for execution. In the example in Listing 3, the web server passes all files with a .php extension to one of two FastCGI servers. The first of the two has IP address 192.168.0.1 and port 1026, and the second sits at the same port on IP address 192.168.0.2.

Listing 3: FastCGI Config for Lighttpd

server.modules += ( "mod_fastcgi" )
fastcgi.balance = "round-robin"
fastcgi.server = (
  ".php" => (
    ( "host" => "192.168.0.1",
      "port" => 1026,
    )
    ( "host" => "192.168.0.2",
      "port" => 1026,
    )
  )
)

On Ubuntu, you just need to install the php7.4-fpm package so PHP is waiting on a socket for incoming PHP code. In this case, you only need to point the web server to the appropriate socket:

fastcgi.server = ( ".php" =>
  (( "socket" => "/run/php/php-fpm7.4" ))
)

Lighttpd always executes the modules in the order in which they appear after server.modules or in lighttpd.conf. Therefore, you should always load the modules that control access first (e.g., mod_auth) and only then load modules that generate and return content (e.g., mod_fastcgi). Otherwise, Lighttpd might skip authentication.

Detailed documentation (Figure 3) of all official modules and the web server can be found in the Lighttpd wiki [5].

The Lighttpd wiki provides an overview of existing modules, which cover all the features that could be important in daily work, such as URL rewriting (mod_rewrite).
Figure 3: The Lighttpd wiki provides an overview of existing modules, which cover all the features that could be important in daily work, such as URL rewriting (mod_rewrite).

Conclusions

The work on Lighttpd is currently limited to maintenance and careful ongoing development of the 1.4 branch. The web server is lagging behind its competitors slightly when it comes to new technologies. Nevertheless, deployment of Lighttpd would make sense if a workgroup needed a web server at short notice, because it can be set up quickly, thanks to its compact and intelligible configuration, and it can be adapted to a team's needs just as quickly through the use of modules.