Running WordPress on OpenBSD 6.6 With OpenBSD's HTTPD

Last Updated: Tue, Jan 21, 2020

Introduction

Given OpenBSD's penchant for security, it only makes sense to power your WordPress website with it, especially because WordPress and PHP tend to be moving targets for script kiddies. Since OpenBSD's httpd is designed principally to serve up static pages, POST operations are reserved for the fastcgi and slowcgi processes. This makes it harder for a rogue actor to potentially break the webserver's process and gain access to your server. POST operations are piped to the fastcgi process and use an external interpreter. This article will discuss not only setting up your WordPress site but some basic maintenance techniques and how to backup and restore your site and its database. Wherever you see example.com as the domain, please replace it with your domain.

Initial Configuration

If you have not already done so, you will need to create a /etc/doas.conf file. The doas command is OpenBSD's easy replacement for sudo. For convenience, I have added the nopass option so that you won't have to re-type your password when using doas. If you prefer not to have this, simply omit nopass.

su -
echo "permit nopass keepenv :wheel" > /etc/doas.conf

Depending on how OpenBSD was packaged for deployment, sometimes the package manager might not have a repository configured. To configure the OpenBSD official repository, we have to create the /etc/installurl file.

doas su
echo "https://cdn.openbsd.org/pub/OpenBSD" > /etc/installurl
exit

Now we have to add PHP and some extra modules that WordPress will need in order to handle things like images and encryption. When prompted, choose to install the newest package of PHP. One thing you have to do is to copy the module ini files from the sample directory to the main one. This has to be done in order to enable the additional PHP modules.

doas pkg_add -r mariadb-client mariadb-server php php-curl php-mysqli php-zip pecl73-mcrypt pecl73-imagick wget
doas cp /etc/php-7.3.sample/* /etc/php-7.3/.

Obtain Let's Encrypt Certificates

In today's world, websites must be served up via SSL or face being downranked by search engines. Fortunately, OpenBSD has a great application called acme-client. The acme-client will automatically generate a new private key and request a new fully valid certificate. The acme-client depends upon having a web server in place so we will need to create a quick default server definition.

With your favorite editor, create /etc/httpd.conf. We will add the other server definitions to the file later. For now this will be enough so that acme-client functions properly.

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

server "default" {
    listen on egress port 80
    root "/htdocs"
    directory index "index.html"

    location "/.well-known/acme-challenge/*" {
        request strip 2
        root "/acme"
    }
}

Also using your favorite editor, create /etc/acme-client.conf.

authority letsencrypt {
    api url "https://acme-v02.api.letsencrypt.org/directory"
    account key "/etc/acme/letsencrypt-privkey.pem"
}

authority letsencrypt-staging {
    api url "https://acme-staging-v02.api.letsencrypt.org/directory"
    account key "/etc/acme/letsencrypt-staging-privkey.pem"
}

domain example.com {
    alternative names { www.example.com }
    domain key "/etc/ssl/private/example.com.key"
    domain full chain certificate "/etc/ssl/example.com.crt"
    sign with letsencrypt
}

Now we have to enable and start httpd. Once we do this, then we can run acme-client and wait for it to obtain our fresh, new certificate. After we do this, we'll add a cron job to automatically request a new one every 7 days so we do not have to worry about expiration.

doas rcctl enable httpd
doas rcctl start httpd
doas acme-client -v example.com

Now we create the cron job. Add this line below the very last entry. In this case, we are telling acme-client to request the new certificate at 1:00AM every Saturday.

doas crontab -e
0       1       *       *       6       acme-client -F example.com && rcctl reload httpd

Configuring HTTPD for WordPress

Now it is time to setup httpd for WordPress. Rather than place our website's definition directly in /etc/httpd.conf, we are going to place it in a separate file called /etc/httpd.conf.example.com and include it in the main configuration file. It is generally a good practice to separate the two, keeping site-wide definitions in your main configuration file and domain-specific settings in a different one.

Add the following line to the bottom of your /etc/httpd.conf file:

include "/etc/httpd.conf.example.com"

Now using your favorite editor, create your /etc/httpd.conf.example.com. For convenience, we are going to create separate log files for your domain. This makes it easier when trying to chase down potential problems with your site.

server "example.com" {
    listen on egress port 80
    alias "www.example.com"

    # Automatically redirect to SSL
    block return 302 "https://$SERVER_NAME$REQUEST_URI"

    log {
            access "access-example.com"
            error "error-example.com"
    }
}

server "example.com" {
    listen on egress tls port 443
    alias "www.example.com"
    root "/htdocs/wordpress"
    directory index "index.php"

    log {
            access "access-example.com"
            error "error-example.com"
    }
    tcp { nodelay, backlog 10 }

    tls {
            certificate "/etc/ssl/example.com.crt"
            key "/etc/ssl/private/example.com.key"
    }
    hsts {
            # max-age value is the number of seconds in 1 year
            max-age 31556952
            preload
            subdomains
    }

    location "/.well-known/acme-challenge/*" {
            root "/acme"
            request strip 2
    }
    location "/posts/*" {
            fastcgi {
                    param SCRIPT_FILENAME \
                            "/htdocs/wordpress/index.php"
                    socket "/run/php-fpm.sock"
            }
    }
    location "/page/*" {
            fastcgi {
                    param SCRIPT_FILENAME \
                            "/htdocs/wordpress/index.php"
                    socket "/run/php-fpm.sock"
            }
    }
    location "/feed/*" {
            fastcgi {
                    param SCRIPT_FILENAME \
                            "/htdocs/wordpress/index.php"
                    socket "/run/php-fpm.sock"
            }
    }
    location "/comments/feed/*" {
            fastcgi {
                    param SCRIPT_FILENAME \
                            "htdocs/wordpress/index.php"
                    socket "/run/php-fpm.sock"
            }
    }
     location "/wp-json/*" {
            fastcgi {
                    param SCRIPT_FILENAME \
                            "htdocs/wordpress/index.php"
                    socket "/run/php-fpm.sock"
            }
    }
    location "/wp-login.php*" {
            authenticate "WordPress" with "/htdocs/htpasswd"
            fastcgi socket "/run/php-fpm.sock"
    }
    location "*.php*" {
            fastcgi socket "/run/php-fpm.sock"
    }
}

For greater security, we are going to implement an additional prompt for a username and password when logging in to the WordPress administration site. Since script kiddies like to repeatedly try and bruteforce WordPress's login, we create an additional login at the web server level. Typically, they get about 5 guesses before WordPress throws a 401 Unauthorized error.

doas su
cd /var/www/htdocs
doas htpasswd htpasswd <user>
doas chown www:www htpasswd
doas chmod 0640 htpasswd
doas rcctl reload httpd

Prepare and Configure PHP and PHP-FPM

We have to make a change in php so that your WordPress installation can send emails. WordPress and some plugins rely on the ability to send emails notifying you of upgrades, alerts, and changes. The inability to send emails can break certain features of WordPress. Since httpd runs in chrooted environment, we have to tell php how to send emails. Furthermore, we have to do some performance tweeks to php-fpm.

Look for the sendmail_path line in /etc/php-7.3.ini and make the following change:

; For Unix only.  You may supply arguments as well (default: "sendmail -t -i").
; sendmail_path =
sendmail_path = /bin/femail -t -i

Look for the following lines in /etc/php-fpm.conf and change them as follows:

pm.start_servers = 5
pm.min_spare_servers = 1
pm.max_spare_servers = 6

The next step is to enable and start php-fpm.

doas rcctl enable php73_fpm
doas rcctl start php73_fpm

Prepare and Configure MariaDB

MariaDB is a drop-in replacement fork of MySQL. We need to do some initial configuration and database preparation work for WordPress.

Before we can use MariaDB effectively, we need to allow the mysql daemon to use more resources than the default. In order to do this, make the following changes to/etc/login.conf by adding this entry at the bottom.

mysqld:\
    :openfiles-cur=1024:\
    :openfiles-max=2048:\
    :tc=daemon:

We have to make some changes in the MariaDB configuration file, /etc/my.cnf. By having the mysql client and server communicate via UNIX domain socket instead of TCP, the memory usage of your server can be kept lower. You do not have to make all of the changes suggested below. The two important ones to change are the socket line and to comment out the bind-address line. This moves the socket inside of the /var/www chroot environment so WordPress can connect to the database. By commenting out the bind-address line, we prevent MariaDB from listening on a TCP port.

[client-server]
socket=/var/www/var/run/mysql/mysql.sock
#port=3306

# This will be passed to all MariaDB clients
[client]
#password=my_password

# The MariaDB server
[mysqld]
# To listen to all IPv4 network addresses, use "bind-address = 0.0.0.0"
#bind-address=127.0.0.1
# Directory where you want to put your data
#data=/var/mysql
# This is the prefix name to be used for all log, error and replication files
#log-basename=mysqld
# Logging
#log-bin=/var/mysql/mariadb-bin
#max_binlog_size=100M
#binlog_format=row
#expire_logs_days = 7
#general-log
#slow_query_log
query_cache_type = 1
query_cache_limit = 1M
query_cache_size = 16M

Now we need to run the MariaDB install binary and enable and start MariaDB. This procedure will set a root password and optionally drop the test database. It's a good idea to follow all of the suggestions in the secure installation stage.

doas mysql_install_db
doas rcctl enable mysqld
doas rcctl start mysqld
doas mysql_secure_installation

Create the WordPress database and database user. Remember to replace <wp_user> with your choice of database username and <password> with a complex password of your choosing.

mysql -u root -p 
CREATE DATABASE wordpress;
GRANT ALL PRIVILEGES ON wordpress.* TO '<wp_user>'@'localhost' IDENTIFIED BY '<password>';
FLUSH PRIVILEGES;
EXIT

Install and Configure WordPress

WordPress has not had an official OpenBSD port for quite some time because it pretty much works right out of the box. Download, extract, and move the WordPress installation folder.

cd /tmp
wget https://wordpress.org/latest.tar.gz
tar xvfz latest.tar.gz
doas mv wordpress /var/www/htdocs/.
doas chown -R www:www /var/www/htdocs/wordpress
doas chmod 0755 /var/www/htdocs/wordpress
cd /var/www/htdocs/wordpress/
find . -type d -exec chmod 755 {} \;
find . -type f -exec chmod 644 {} \;

We have to copy /etc/resolv.conf and /etc/hosts to a directory we are going to create called /var/www/etc. This is so that WordPress can successfully reach the marketplace. You will need this in order to download plugins and themes via the WordPress admin site. It is also important for the Jet Pack plugin to work properly.

doas mkdir /var/www/etc
doas cp /etc/hosts /var/www/etc/.
doas cp /etc/resolv.conf /var/www/etc/.

From here, browse to your WordPress website using https via the URL that you specified in the web server definition. If everything is working correctly, you should see the WordPress install wizard. When you get to the option to specify a database server, you should use localhost:/var/run/mysql/mysql.sock

Once WordPress has been installed, it's time to set up the permalinks so that they look more SEO friendly. From the WordPress admin screen, go to Settings -> Permalinks. Click on Custom Structure and type /posts/%postname%. After making this change, click the Save Changes button. You now have much nicer looking links. For example, a permalink will look like this: https://example.com/posts/example-blog-post

From here, you should have a basic website ready to go. Make certain you install plugins like Jet Pack and WP-Super Cache. The WP-Super Cache plugin helps speed up your website by caching web pages and eliminating constant database lookups and JetPack gives you some excellent viewership statistics.

Backing Up Your WordPress Website and Database

It should go without saying that backing up your website and database is very important. Thankfully, this is a relatively easy thing to do. Backup both to your home directory and then you can copy them via scp to another location. You can also create a snapshot via the Vultr Control Panel. It is a good idea to do both.

cd /var/www/htdocs
tar cvfz wordpress.tgz wordpress/
cp wordpress.tgz /home/user
mysqldump -u root -p wordpress > wordpress.sql && gzip wordpress.sql

Restoring Your WordPress Website

If your database became corrupted and a restore is necessary, perform the following:

gunzip wordpress.sql.gz
mysql -u root -p wordpress
DROP USER '<user>'@'localhost';
DROP DATABASE wordpress;
CREATE DATABASE wordpress;
GRANT ALL PRIVILEGES ON wordpress.* TO '<wp_user>'@'localhost' IDENTIFIED BY '<password>';
FLUSH PRIVILEGES;
EXIT
mysql -u root -p wordpress < wordpress.sql

If you've made a change to a WordPress script file that broke something, you can always reinstall WordPress via the admin control panel. Look for the Updates section and click on the link. Look for a button labeled Re-install Now. This will at least fix what is broken but most of your configuration should remain intact.

If your database is in good shape, but you've accidently edited a file and broke things to the point where you cannot even get to the WordPress admin console, then do the following:

rm /var/www/htdocs/wordpress
cp /home/user/wordpress.tgz /tmp
tar xvfz wordpress.tgz
mv wordpress /var/www/htdocs/.
chown -R www:www /var/www/htdocs/wordpress
cd /var/www/htdocs/wordpress
find . -type d -exec chmod 755 {} \;
find . -type f -exec chmod 644 {} \;

Want to contribute?

You could earn up to $300 by adding new articles