OpenBSD Blog #12: Multiple OpenBSD httpd instances with multiple chroots

Page created: 2026-03-05
Updated: 2026-03-31

Go back to my OpenBSD page for more entries.

OpenBSD’s httpd web server calls chroot when it runs so that it cannot access files outside of the specified directory. This is awesome for security, but annoying for my weirdo internal home setup where I might want to serve media from a large capacity drive mounted outside of /var/www/ or serve a local test copy of my website straight out of the source directory.

I tried to get around this, but chroot is quite effective. That’s good!

As near as I can tell, the correct (and only) way to deal with this strange situation is to go ahead and run an entirely separate instance of httpd listening on an alternative port (8080 is a popular choice).

In the next two sections, I have an example that I created as my intial test.

At the bottom of this page, I describe the setup I actually ended up with for serving my local test instance of ratfactor.com (this website).

A separate httpd.conf example

I made a copy of /etc/httpd.conf called /etc/httpd-media.conf and invoked it with:

$ httpd -f /etc/httpd-media.conf

Here’s httpd-media.conf:

# web server cannot access anything outside this directory
chroot "/big-drive/media"

server "phobos2" {
    listen on * port 8080

    # web server will look here for files (default is '/htdocs')
    root "/"

    directory index "index.php"

    location "*.php" {
        fastcgi socket "/run/php-fpm.sock"
    }
}

I manually created the following directories as well:

  • /big-drive/media/logs/

  • /big-drive/media/run/

The logs directory will contain the access and error logs. The run directory is for the PHP-FPM socket, which I’ll explain next.

PHP-FPM

You’ll notice I’m also running PHP. Feel free to skip this section if you aren’t.

PHP-FPM also restricts itself to a chroot setting.

So I also made a copy of /etc/php-fpm.conf called /etc/php-fpm-media.conf and invoked it with:

$ php-fpm -y /etc/php-fpm-media.conf

The relevant portions of php-fpm-media.conf:

    ...
listen = /big-drive/media/run/php-fpm.sock
    ...
chroot = /big-drive/media

You will also likely have noticed that the socket used to connect from httpd to PHP_FPM must be under the chroot and is traditionally at run/php-fpm.sock.

Script it

Here’s what I actually have for my local test copy of ratfactor.com. You’ll notice that I moved both .conf files into the root of the "project" area to keep /etc/ clean.

Files:

~/wiki/ratf/             <-- set as chroot in both confs
~/wiki/ratf/src/         <-- the web content root directory
~/wiki/ratf/logs/        <-- create directory for logs
~/wiki/ratf/run/         <-- create directory fpm socket
~/wiki/ratf/httpd.conf
~/wiki/ratf/php-fpm.conf

I also write a script, ratf-restart, which stops and starts these special httpd and php-fpm instances while leaving the main instances started by the main rc.d(8) scripts alone!

#!/bin/ksh

# pkill args:
#   f = match full args list!
#   q = quiet
#   l = print long format (debugging)
#   I = prompt to kill (debugging)
#
# note: you can see what was matched by the pkills below by
# running the same thing with "pgrep -l -f ..."


printf "Stopping httpd..."
doas pkill -qf 'httpd.*ratf'
if [ $? == 0 ]
then
    echo "Killed"
else
    echo "Not running"
fi

printf "Stopping php-fpm..."
doas pkill -qf 'php-fpm.*ratf'
if [ $? == 0 ]
then
    echo "Killed"
else
    echo "Not running"
fi

echo "Starting php-fpm..."
doas php-fpm-8.4 -y /home/dave/wiki/ratf/php-fpm.conf

echo "Starting httpd..."
doas httpd -f /home/dave/wiki/ratf/httpd.conf

I can run that after starting the computer (though the uptime is epic, so this is rare) or any time I make changes to either .conf file.

It looks like this when I run it:

Stopping httpd...Killed
Stopping php-fpm...Killed
Starting php-fpm...
Starting httpd...

Really happy with this. Just automated enough to take the pain out of it without being too automated so I forget how things work.