This article first appeared on this date in O’Reilly’s ONLamp.com online publication. The content was deleted sometime in 2019 but I was lucky enough to find a copy in the WayBack Machine. I reformatted the text to fit the style of this site and fixed broken links, but otherwise the content is a verbatim reproduction of what was originally published.


The Apache HTTP Server is the most popular web server due to its functionality, stability, and maturity. However, this does not make it suitable for all uses: slow machines and embedded systems may have serious problems running it because of its size. Here is where lightweight HTTP servers come into play, as their low-memory footprints deliver decent results without having to swap data back to disk.

Similarly, these small HTTP servers are suitable to serve static content efficiently so as to allow Apache, mod_perl`, mod_python, or even servlet containers to handle dynamic requests without tying up memory-hungry children to serve small images. In other words, these applications can serve as a complement to your existing full-featured web server, not as a replacement.

One of these servers is thttpd, a simple, small, portable, fast, and secure HTTP server. Among its features are support for the HTTP/1.1 standard, CGIs, virtual hosts, and IPv6. This article shows how to install and configure this software under NetBSD. I chose NetBSD not only because it is my preferred OS, but also because it has the ability to run on the most disparate old hardware, where thttpd shows its strengths. I had a Macintosh Performa 630 (a 68LC040 chip at 33MHz) running NetBSD/mac68k 2.0 with thttpd on top of it, serving pages to my home network nicely.

Use Cases

I previously mentioned a few use cases for thttpd, but there are several other scenarios in which it may be useful:

  • Slow machines: Machines with old hardware that are able to run a Unix-like operating system (such as NetBSD) are often powerful enough to serve static content with thttpd.
  • Heavily loaded machines: You may have a very powerful machine that runs other heavy processes, such a DBMS, yet you need it to serve some web content (statistics, for example). In this case, thttpd can do well.
  • Simple requirements: Sometimes you may want to provide web content to the public, but you do not need much of the fancy stuff provided by powerful servers such as Apache. In this case, a lightweight server may be enough for your needs. Furthermore, given their smaller code sizes, they have fewer chances to fail and you can audit their code more easily.
  • Serving static content alongside a powerful server: You may have a server running Apache with a load of modules, parsing very complex dynamic pages. These pages often need to include other files, most commonly static images. In this case, thttpd can serve the static data alongside Apache, which will exclusively handle the complex content. There is a section dedicated to this specific use case later.

Installing thttpd

Given my choice of NetBSD, I used pkgsrc, the NetBSD Packages System, to install thttpd. However, please note that you don’t have to use NetBSD to use pkgsrc, so all the examples given here can apply to other systems with few hassles; otherwise, use your packaging system of choice to get thttpd.

Focusing on pkgsrc, there are two ways to install thttpd: from sources or from binaries. Sources are appropriate if you do not want to use the default settings, you want to configure the build (perhaps with optimization flags), or you need thttpd on a system for which there are no binary packages. On the other hand, binaries are usually fine for all other cases. I’ll cover both approaches.

Assuming you have pkgsrc under /usr/pkgsrc/, installing from sources is trivial:

# cd /usr/pkgsrc/www/thttpd/
# make install && make clean

If you want to go the binary route, make sure that a prebuilt binary package is available for your platform. (See the NetBSD package FTP site.) In my case, I used NetBSD/i386 2.0 with the latest stable pkgsrc branch, pkgsrc-2005Q2, so the following did the trick:

# PKG_PATH=ftp://ftp.NetBSD.org/pub/NetBSD/packages/pkgsrc-2005Q2/NetBSD-2.0/i386/All pkg_add thttpd

No matter which procedure you choose, you will see something like the following after a successful installation:

===========================================================================
Installing files needed by thttpd-2.25bnb3:

        /usr/pkg/etc/thttpd.conf
            [/usr/pkg/share/examples/thttpd.conf]

===========================================================================
===========================================================================
The following files should be created for thttpd-2.25bnb3:

        /etc/rc.d/thttpd (m=0755)
            [/usr/pkg/share/examples/rc.d/thttpd]

===========================================================================

===========================================================================
$NetBSD: MESSAGE,v 1.2 2001/11/19 16:23:16 jlam Exp $

In order to use makeweb:

        chgrp <group> makeweb
        chmod 2755 makeweb

The suggested group is "www".
===========================================================================

Note that the exact output depends on your mk.conf settings. I used all the default settings, so the program installed under /usr/pkg/ with its configuration file stored as /usr/pkg/etc/thttpd.conf. Keep the correct paths in mind before proceeding.

Creating the Server Directory

Now that the server is installed, it is time to configure it. The very first thing to change is the documents’ root directory (DocumentRoot, as Apache httpd calls it). The default is /usr/pkg/share/thttpd/, a far from optimal value in most cases due to its dynamic nature. (Remember that /usr/ is often mounted read-only.) A better place is, for example, /home/www/, so change the configuration to use this directory instead. Open the configuration file (/usr/pkg/etc/thttpd.conf) in your favorite editor and change the dir variable to point to the correct directory, like this:

dir=/home/www

Once this is done, create the directory itself and a simple page inside it:

# mkdir /home/www
# cat >/home/www/index.html
<?xml version="1.0"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
  <head>
    <title>Welcome</title>
  </head>

  <body>
    <p>thttpd is running!<p>
  </body>
</html>
^D

Note that unless you create a default index.html file, people will be able to see the contents of the directory, which may be inappropriate.

Setting Up Permissions

Before running thttpd, which is already possible at this point, it’s worthwhile to tighten permissions a bit. As in any networking-aware application, it is a good idea to run it without superuser privileges, aiming to lessen damage to the system in case of attack. Therefore, my goal is to run the server process under a regular user account. Create the www group, to which any webmasters will belong, and the thttpd user, the account that will run the daemon.

# useradd -g nobody -d /home/www thttpd
# groupadd www

Notice how the thttpd account uses the nobody group: www is a group for web masters; the daemon has no business writing to the documents directory.

Now that the account is ready, tell thttpd to use it:

# echo user=thttpd >>/usr/pkg/etc/thttpd.conf

Finally, change the permissions of the documents directory to let any member of the www group modify the files inside it:

# chown -R root:www /home/www/
# chmod 775 /home/www/
# chmod 664 /home/www/index.html

Enabling the Server

Now you are ready to start the server, usually by using the provided rc.d script. First, copy this script to the appropriate system directory, /etc/rc.d/, so that it is available at boot time:

# cp /usr/pkg/share/examples/rc.d/thttpd /etc/rc.d/

Once it is in place, enable it. Now is another opportunity to make things a bit better, security-wise: tell the daemon to chroot itself into the documents’ root directory after startup. This is easy to do on NetBSD by passing the -r flag to the daemon. With this in mind, modify /etc/rc.conf:

# cat >>/etc/rc.conf
echo thttpd=YES
echo thttpd_flags=-r
^D

You’re done. Start the server with the command:

# /etc/rc.d/thttpd start

Before continuing, it’s helpful to verify that everything works correctly. First connect to http://localhost/ (note that this link will work only if you run this from the machine on which you installed thttpd), and check that the web page you wrote before appears. Secondly, make sure that the -r flag was effective; you do not want to discover several months later that what you thought was chrooted in fact was not. Check this with help from the fstat command, whose purpose is to show the status of all open files. Search for the root file used by the daemon:

# fstat | grep ^thttpd | grep root
thttpd   thttpd      1206 root /home    2351520 drwxrwxr-x 512 r

What the output shows is that the command is being executed under the directory pointed to by the 2351520 inode, living under the /home filesystem (which is a separate partition). To verify that the inode number belongs to your documents’ root directory, use the ls utility:

# ls -lidF /home/www/
2351520 drwxrwxr-x  2 root  www  512 Jun  4 16:35 /home/www/

The number on the first column matches the inode number shown by fstat. This daemon is chrooted inside the appropriate directory.

Congratulations! The server is now correctly configured and is up and running. But wait—you’re not done yet.

Running CGI Scripts

The easiest way to run CGI scripts from within the just-installed server is to disable the chroot feature (by removing the thttpd_flags line from /etc/rc.conf). However, considering all the security advantages the feature brings, this is a bad idea. You can still run CGIs inside the chroot jail, although it is a bit more complicated.

Create a simple CGI application for testing. Shell scripting is used in order to make explanations easier; you can use any appropriate language you prefer.

# mkdir /home/www/cgi-bin
# cat >/home/www/cgi-bin/hello.sh
#!/bin/sh

echo "Content-type: text/html"
echo
echo "Hello, world!"
^D
# chmod +x /home/www/cgi-bin/hello.sh

With this in place, add the following form code to the web page you created before, /home/www/index.html:

<form name="hello" method="post" action="/cgi-bin/hello.sh">
  <input type="submit" value="Run it!" />
</form>

If you try to access the page and click on the button, thttpd will spit out an error. What happened? The script could not find the shell interpreter, /bin/sh, specified on the first line. (Being chrooted, thttpd considers all paths relative to /home/www/.) To solve this, copy the file, as well as any other required stuff, inside the jail. (It would be a lot easier with a static build of sh.) A simple way to do this is:

# cd /home/www
# mkdir bin
# cp /bin/sh bin
# ldd bin/sh
bin/sh:
        -ledit.2 => /lib/libedit.so.2
        -ltermcap.0 => /lib/libtermcap.so.0
        -lc.12 => /lib/libc.so.12
# mkdir lib
# cp /lib/libedit.so.2 lib
# cp /lib/libtermcap.so.0 lib
# cp /lib/libc.so.12 lib
# mkdir libexec
# cp /libexec/ld.elf_so libexec
# chown -R root:wheel bin lib libexec

Depending on the applications you need inside the chroot, things will get more complicated: you may need to create device files, a user database, pipes, and so on. As an exercise, try to make Perl work.

After putting everything into the correct place, try clicking on the button again and all should be fine: you should receive the “Hello, world!” message in response.

Virtual Hosts

thttpd supports virtual hosts. Setting them up is extremely easy. The first thing you need to do is make all the domain names you want to serve point to the machine running thttpd. This basically means setting up extra CNAMEs for the main host’s name, but that is a DNS-specific task. Refer to your name server’s documentation for more information, or ask your DNS administrator to do it for you.

Having the domain names adjusted accordingly, the next step is to enable support for virtual hosts. Add the vhost option to the configuration file (on a line of its own):

# echo vhost >>/usr/pkg/etc/thttpd.conf

After adding this option (and restarting the server), the behavior of the documents directory (/home/www/) will change completely: it will no longer hold HTML pages nor any other file. Instead, it will have a set of subdirectories, each one associated to one of the virtual hosts served. The name of each directory is the name of the virtual host’s name or IP address. For example, supposing you want to serve web pages for the domains products.example.com and support.example.com, your documents directory could contain:

# ls -lF /home/www/
total 4
drwxr-xr-x  2 root  www  512 Sep  4 22:48 products.example.com/
drwxr-xr-x  2 root  www  512 Sep  4 22:48 support.example.com/

Yes, it is that simple!

Running Alongside Other Servers

The beginning of the article mentioned that another use of thttpd is to serve static content alongside powerful web servers. The easiest way to do that is to start thttpd on a different port than the other servers, which requires adding an extra line to the configuration file:

# echo port=8080 >>/usr/pkg/etc/thttpd.conf

I assume that if you have done this, it is because you have a full-featured web server in the same machine as thttpd. Therefore, you need to modify the web pages it serves to point to the static content now served under http://localhost:8080/.

Conclusion

This article has shown how to install and configure the lightweight thttpd web server on a step-by-step basis. This application has several other features; if you want to learn more, spend some time reading the thttpd manual or the accompanying thttpd notes, which deal with several design decisions—an interesting read, certainly.

Of course, there are many more simple HTTP servers available on the internet. If this one does not fit your needs, there probably is another one that does.