October 31, 2006

Lightweight fnord serves HTTP admirably

Author: Manolis Tzanidakis

I was looking for a lightweight Web server to run on my ARM-based Linksys NSLU2 network storage device in order to share a few custom packages I've built for Debian and Arch Linux among the systems on my home network. After playing around with Apache, LightTPD, and thttpd, I tried fnord and never looked back.

Fnord is a small GPL-licensed Web server written by Felix von Leitner. It doesn't have the impressive list of features Apache has, but it still manages to be fast and scalable. It supports virtual domains, IPv6, CGI (Common Gateway Interface) for running server-side scripts, HTTP redirection, and content type matching. Fnord also transparently offers gzip-compressed HTML files if the client Web browser supports it; that is, if you ask for index.html, it will instead serve a compressed index.html.gz and thus save you some bandwidth. The same thing happens when you ask for a GIF image file; in that case it will offer a PNG file, which has better compression and is patent-free.

The fnord binaries installed by the Debian package are less than 30KB in size and statically linked (that is, they have no dependencies on any external libraries). Compare that to the 285KB of the Apache 1.3.33 binary on my Debian Sarge server, which depends on a dozen libraries; a statically linked Apache binary might be about 1MB in size. Fnord's small size is the result of linking with diet libc, a C standard library optimized for small size, which was also written by von Leitner. Diet libc supports all the major hardware platforms but is available only for Linux. Diet libc does not include some functions of glibc, the de facto C library used by most Linux distributions, and thus it's not compatible with every available program; you can check the list of exported symbols for details.

Packages for fnord are available for all three Debian branches (stable, testing, and unstable) and are built with diet libc. Gentoo includes fnord in its Portage tree but doesn't link it with diet libc, though it is also available in Portage. FreeBSD and NetBSD users can find fnord in ports and pkgsrc respectively, but since diet libc is Linux-specific fnord is linked with the stock libc for those operating systems.

If you want to manually compile fnord on a Linux system, build and install diet libc with make; make install first and then build fnord by running make. If you run make install, fnord will be installed in /command, by default; it seems that von Leitner embraces the controversial ideas of D.J. Bernstein -- author of qmail and other programs -- about Unix file system layouts. However, you can still manually copy all fnord* binaries to a directory of your choice, such as /usr/local/sbin. If you don't want to link it with diet libc, run make DIET= instead to create binaries dynamically linked to your system's libc.

Fnord does not run as a standalone server and thus requires a super server -- that is, a daemon that listens for connections on a network port -- in our case port 80 -- and launches the real server daemon when a client connects to that port. You can use a package such as inetd or tcpserver (part of Bernstein's ucspi-tcp). Fnord can also be monitored and supervised (for example to restart it if it crashes) using daemontools. I chose to use ipsvd and runit instead of ucspi-tcp and daemontools, mainly due to Bernstein's restrictive license for the latter two, and also because support for ipsvd and runit is integrated in the Debian package for fnord.

Supervising programs such as daemontools and runit need a run script for each daemon they start. You can manually create one for fnord or use the fnord-conf shell script that is included in the source tarball. The Debian package supplies a slightly changed fnord-conf script that utilizes runit and ipsvd and also supplies fnord-ssl-conf for creating SSLv3-encrypted HTTPS services with fnord; read /usr/share/doc/fnord/README.Debian for more information. Before running fnord-conf you need to issue the following commands to add two users: fnord for running the daemon and fnordlog for the logging process. Both users will be members of the nofiles group:

id -g nofiles || groupadd nofiles
useradd -g nofiles -s /bin/false -d /etc/fnord -c "fnord user" fnord
useradd -g nofiles -s /bin/false -d /etc/fnord -c "fnord log user" fnordlog

You could use existing users, such as nobody or daemon instead, but it's better security practice to add new users exclusively for running fnord. Now you can create run scripts in /etc/fnord that run fnord as the fnord user and its log process as the fnordlog user. Issue the command fnord-conf fnord fnordlog /etc/fnord /var/www IP_ADDRESS. The fnord daemon will serve documents located in /var/www and listen for connections on IP_ADDRESS. Replace that variable with your server's IP address, or remove it completely to listen on all available addresses. To start the server, create a symlink of the /etc/fnord directory in /var/service (or /service if you use daemontools) with ln -s /etc/fnord /var/service (or /service).

Create a /var/www/default directory and place your HTML documents inside. If you want to create a virtual host, such as virtual.mydomain.tld, create a /var/www/virtual.mydomain.tld:80 directory and place your documents there; the default directory is used when no virtual hosts are defined. If you want to create a site redirection -- let's say, when users ask for http://virtual.mydomain.tld they will be redirected to http://www.linux.com -- create a dangling symlink with ln -s http://www.linux.com /var/www/virtual.mydomain.tld:80 and you're done. You can do the same thing with files; if a requested file is not found but a dangling symlink with the same name exists, fnord will redirect you to the contents of that symlink. For example, run ln -s http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.18.tar.bz2 /var/www/default/linux-2.6.18.tar.bz2. If you request the file linux-2.6.18.tar.bz2 from your fnord server, you will download it from www.kernel.org instead.

You can configure fnord to use authentication -- read the README.auth file included in the source tarball (or in /usr/share/doc/fnord if you use Debian) for instructions. I haven't tried running any CGI scripts on my server, but the README file provides instructions on how to implement CGI support, among other information.

I've been running fnord on my NSLU2 for about a month without any problems. It consumes only a fraction of the limited resources of NSLU2 (which has a 266MHz CPU and 32MB of RAM) and gets the job done. I run fnord on my home network so I don't know how well it scales on a busy server, but a graph on the project's homepage indicates that it works.

Click Here!