This article focuses on httpd, the Web server. If you’re looking for a more comprehensive article that also covers relayd, the reverse proxy, you’ll find it on my other blog, OpenBSD Desktop .

OpenBSD httpd

Just to be clear, we are talking here about OpenBSD httpd, not to be confused with Apache httpd, which is generally referred to as Apache. The latter can be installed via pkg, the package manager.

OpenBSD httpd is a Web server developed by the team behind OpenBSD. For an operating system used primarily on servers, including a Web server is both logical and practical. If required, it can be paired with relayd, the reverse proxy / load balancer included with OpenBSD, which I will cover in a separate article.

Note: doas is the OpenBSD equivalent of sudo, so when a command is preceded by doas, it is executed as an administrator.

PHP installation

Since we want to run a website written in PHP, let’s start by installing it. Simply run:

doas pkg_add php

and to select the PHP version if several are available. It is also possible to install extensions, for example:

doas pkg_add php-pdo_mysql

and to enable them:

doas ln -s /etc/php-8.5.sample/pdo_mysql.ini /etc/php-8.5/pdo_mysql.ini

To check that PHP is installed correctly, type php -v.

The Web server uses PHP FPM, which is installed alongside PHP; all you need to do is enable and start the service:

doas rcctl enable php85_fpm && doas rcctl start php85_fpm

That’s it for PHP; let’s move on to httpd.

Starting the httpd server

Before you begin, you should be aware that httpd and PHP FPM are chrooted into /var/www/, which means they cannot see the rest of the system. When they attempt to access the root directory, /, they actually see the contents of /var/www/. This is a security measure: if an attacker exploits a vulnerability in your application (or in httpd) and manages to execute commands, they will not have access to all the files on the server; they will be restricted to /var/www/. You do not need to do anything to enable this; it is already in place by default.

Since we’re going to be using it, let’s start the httpd service:

doas rcctl enable httpd

Let’s move on to the configuration, which is located at /etc/httpd.conf.

First, we’ll configure it for HTTP, so that we can generate a TLS certificate later.

types { include "/usr/share/misc/mime.types" }

# Here we are configuring for HTTP only
# We are configuring phpmyadmin.abosec.fr; the DNS configuration must have been
# set up beforehand
server "phpmyadmin.abosec.fr" {
    # Listen on the Internet, port 80
    listen on egress port 80
    # The root is /var/www/htdocs/phpmyadmin/
    # Keep in mind that httpd is chrooted in /var/www/
    root "/htdocs/phpmyadmin"
    # To generate Let's Encrypt (LE) certificates, acme-client generates
    # files, stored in /var/www/acme/, which are then accessed by
    # the LE infrastructure. The block below simply states that any URL
    # resembling http://phpmyadmin.abosec.fr/.well-known/acme-challenge/<file>
    # looks for <file> in the /var/www/acme directory, referred to as /acme
	# because httpd is chrooted into /var/www/.
    location "/.well-known/acme-challenge/*" {
        root "/acme"
        request strip 2
    }
}

We (re)start httpd with

doas rcctl -d restart httpd

We are ready to generate the TLS certificate.

Generating the TLS certificate

Note: It is important to check that the /var/www/acme/ directory is readable by httpd. Simply create a file called /var/www/acme/test.txt containing ’test’ and access the URL ‘ http://phpmyadmin.abosec.fr/.well-known/acme-challenge/test.txt' . If you cannot see “test”, adjust the folder permissions.

Next, configure acme-client in the /etc/acme-client.conf file. acme-client allows you to generate and regenerate certificates using the ACME protocol. On Linux, certbot is often used for this purpose.

# We define a certificate authority, in this case Let's Encrypt
authority letsencrypt {
    api url "https://acme-v02.api.letsencrypt.org/directory"
    account key "/etc/acme/letsencrypt-privkey.pem"
}

# Here we define the Let's Encrypt test server
# This may be useful for checking that our installation is working before
# moving to production
authority letsencrypt-staging {
    api url "https://acme-staging-v02.api.letsencrypt.org/directory"
    account key "/etc/acme/letsencrypt-staging-privkey.pem"
}

domain phpmyadmin.abosec.fr {
    # When you want to include several domains in a certificate,
    # for example the www subdomain, you use 'alternative names'
    #alternative names { www.phpmyadmin.abosec.fr }
    domain key "/etc/ssl/private/phpmyadmin.abosec.fr.key"
    domain full chain certificate "/etc/ssl/phpmyadmin.abosec.fr.fullchain.pem"
    # We choose the certification authority to use
    sign with letsencrypt
}

Once configured, simply run

doas acme-client phpmyadmin.abosec.fr

We can then run it as a cron job to renew the certificate automatically.

If everything went well, we can amend the httpd configuration to run over HTTPS.

httpd configuration

Back in /etc/httpd.conf, we adjust the configuration so that the site redirects from HTTP to HTTPS.

# Here we configure it for HTTPS only
# This is the main site
server "phpmyadmin.abosec.fr" {
    listen on egress port 443

    # The keys are loaded when the server boots, before the chroot is applied
	tls {
        certificate "/etc/ssl/phpmyadmin.abosec.fr.fullchain.pem"
        key         "/etc/ssl/private/phpmyadmin.abosec.fr.key"
    }

    root "/htdocs/phpmyadmin"
    location "*.php" {
        # Optional: basic authentication to prevent bots from scanning phpMyAdmin
        #authenticate with "/auth-files/phpmyadmin"
        # Everything ending with .php is handled by PHP
        fastcgi socket "/run/php-fpm.sock"
    }

    # ACME is now handled via HTTPS
    location "/.well-known/acme-challenge/*" {
        root "/acme"
        request strip 2
    }
}

# He we configure it for HTTP only
server "phpmyadmin.abosec.fr" {
    listen on egress port 80
    root "/htdocs/phpmyadmin"
    # We redirect to the requested page, on HTTPS
    block return 301 "https://phpmyadmin.abosec.fr$REQUEST_URI"	
}

We restart again httpd with

doas rcctl -d restart httpd

And there we go, the Web app is now available.


Ready to put your server into production? Find out how I can migrate and host your OpenBSD infrastructure , or let’s discuss it directly .