PHP/MySQL on OpenBSD's relayd-based httpd

Introduction

OpenBSD comes with a web server called httpd, which was based on relayd. We're going to tackle setting up OpenBSD to serve "LAMP" (or would it be OHMP?) web apps with the built-in httpd, MariaDB (a MySQL fork in OpenBSD Ports) and PHP-FPM.

As of October 24, 2018, this guide has been updated for OpenBSD 6.4. It was tested on the amd64 architecture. It will likely work on i386, sparc64, arm64 and others, but package version numbers may vary slightly.

Preparation

First, install OpenBSD. Be sure to create a user-level account for yourself during the installation process, and I'd recommend disabling remote root logins while you're at it. This user account will be added to the wheel group. On BSD systems, wheel group is comparable to an administrator group, granting access to use the su command, etc. You can add other trusted users to this group later on.

OpenBSD no longer includes sudo in the base install. It's still available as a package if you must use it, but we'll be making use of the replacement doas(1) tool, which is similar to sudo in several ways. Create a file called /etc/doas.conf. The man pages for doas and doas.conf are quite helpful, but as a quick and dirty way to get up and running with doas, there's a minimal doas.conf file below. You can also add "nopass" after "permit" if you don't want to be prompted for a password. I don't recommend doing that to a production environment.

permit :wheel

If you have a hard time with typing "sudo" instead of "doas", you might want to add an alias to your shell profile.

The /etc/installurl file tells OpenBSD where to find binary packages. If you installed sets from an official OpenBSD mirror, the installurl file likely already exists with the mirror you installed from. Otherwise, create this file and add a mirror to it.  My /etc/installurl looks like this:

https://ftp5.usa.openbsd.org/pub/OpenBSD

Install Packages

OpenBSD includes the Suhosin Hardened PHP patches in their default PHP package, which is nice. Httpd will require the use of PHP with FastCGI. We'll be using php-fpm for this, which was merged into the main PHP packages recently. Since OpenBSD's package manager automatically installs dependencies, you can get away with this command, which should install PHP, mariadb client tools, and everything else we need to get our PHP web application server up and running:

doas pkg_add php-mysqli mariadb-server

You will be prompted for which version of PHP you want to install. Unless you have a good reason not to, it's best to go with the newest (highest version number) available. In OpenBSD 6.4, that's PHP 7.2.10. Enable php-mysqli by symlinking the sample mysqli.ini to the /etc/php-7.2 directory.

doas ln -sf /etc/php-7.2.sample/mysqli.ini /etc/php-7.2/mysqli.ini 

Configure MariaDB, PHP-FPM and httpd

Setup and secure MariaDB with the below commands:

doas /usr/local/bin/mysql_install_db
doas rcctl start mysqld
doas /usr/local/bin/mysql_secure_installation

Follow the prompts and choose a good password for the root user while you're at it.

With the new php-7.2 packages, php-fpm has a new configuration structure, and it won't even start without a little manual intervention. It has an include directive for /etc/php-fpm.d/*.conf for the FPM pools, but that directory doesn't exist, and no default pools are defined in the base php-fpm.conf.

doas mkdir /etc/php-fpm.d

I'm borrowing the pool config I'm using on one of my production web servers. You may want to adjust some of these values, but this pool config has been stable for me for years. Put this in /etc/php-fpm.d/www.conf:

[www]
user = www
group = www
listen = /var/www/run/php-fpm.sock
listen.owner = www
listen.group = www
listen.mode = 0660
pm = dynamic
pm.max_children = 25
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
chroot = /var/www


Next, we need a very basic configuration for httpd. I was surprised at how little configuration was needed to get httpd working. This is just enough to get files ending in .php redirected to FPM, while serving up static content for everything else in /var/www/htdocs/. If you want something other than "index.php" to be your default index, you'll have to change it. See the man page for httpd.conf for details. Put the below lines in /etc/httpd.conf (you'll need to create it from scratch, but this was based on the example in /etc/examples/httpd.conf modified for php-fpm use):

ext_if="egress"

types { include "/usr/share/misc/mime.types" }
server "default" {
   listen on $ext_if port 80
   directory {index "index.php" }
   location "/*.php*" {
       root { "/htdocs" }
       fastcgi socket "/run/php-fpm.sock"
   }

   location "/*" {
       root { "/htdocs" }
   }
}

That's almost all there is to it. Just tell OpenBSD to start the httpd, php-fpm and mysqld services with rcctl enable:

doas rcctl enable httpd
doas rcctl enable php72_fpm
doas rcctl enable mysqld

You can manually start all these services (mysqld is already running because we started it earlier), or just reboot to make sure everything works.

doas rcctl start httpd
doas rcctl start php72_fpm

Set up LAMP style web-apps

Since the web environment is in a chroot restricted to /var/www and the MySQL socket is not inside /var/www, the easiest way to get database access is to create your MySQL users for a host of "127.0.0.1" instead of localhost. This forces MySQL connections over TCP. There are some complicated ways of getting the socket into /var/www, such as forcing MySQL to write it inside /var/www or creating hard links to the socket. Those are beyond the scope of this article. My first test was a simple PHPInfo file saved as index.php.


I've managed to install various pre-packaged content management systems and my own PHP web sites on OpenBSD's httpd without any problems. Some apps (like OwnCloud) seem to require some extra work. I have managed to get WordPress working with pretty permalinks as well.

Virtual Hosts

Setting up virtual hosts is also easy. As with other web servers, the first entry will be the default if the host header doesn't match any other entries (e.g. a visitor using the IP address only. Just add as many Server clauses as you need, and set them up with web roots relative to the /var/www chroot.  Here's an example for 3 virtual hosts with slightly different configurations:

server "things.h-i-r.net" {
 listen on $ext_if port 80
 directory {index "index.php" }
 location "/.ht*"   { block }
 location "/.git*"  { block }
 location "/.svn*"  { block }
 location "/*.php*" {
     root { "/sites/things.h-i-r.net" }
     fastcgi socket "/run/php-fpm.sock"
 }
 location "/*" {
     root { "/sites/things.h-i-r.net" }
 }
}

server "stuff.h-i-r.net" {
 listen on $ext_if port 80
 directory {index "index.html" }
 location "/*" {
     root { "/sites/stuff.h-i-r.net" }
 }
}

server "h-i-r.net" {
 listen on $ext_if port 80
 directory {index "index.php" }
 location "/*.php*" {
     root { "/sites/h-i-r.net" }
     fastcgi socket "/run/php-fpm.sock"
 }
 location "/*" {
     root { "/sites/h-i-r.net" }
 }
}