My new web setup

This is an OpenBSD post

My website suddenly died so I decided to rethink the way it’s hosted, thanks to laziness.

When it happened I had a pretty weird setup. I have two physical “servers”: a Raspberry Pi 3 B and a Dell Optiplex 780 USFF. The former used to host the website using Caddy, while the latter hosted the gateway to my web “services” using Nginx in an ESXi VM, on Alpine Linux. So the only job of that Nginx instance running on the “big” server was to redirect all https traffic to the Pi. That’s all. And one day it stopped working.

I was too lazy to investigate the problem so I just remade the whole thing from scratch. I wiped the Nginx VM and installed OpenBSD on it.

I’m not going to cover installing OpenBSD because it’s a very easy and painless process. I can only praise it.

So, the task was to move the website to the VM so the RPi gets to host only the pub. My plan for the future is to make a VM for hosting files and stuff. Until then, the pub stays on the pi.

That means I needed to:

  1. Install a web server program, move the data there.
  2. Acquire a certificate from Let’s Encrypt.
  3. Make the web server program to redirect all data for pub.tudorr.xyz to the Pi.

Install the web server program

It was my chance to try OpenBSD’s own httpd.

No need to install it, it’s already there.

Here is the final config:

server "tudorr.xyz" {
        listen on localhost port 81
        root "/htdocs/tudorr.xyz"

        location "/.well-known/acme-challenge/*" {
                root "/acme"
                root strip 2
        }
        location "/blog/*" {
                root "/htdocs/blog/_site"
                root strip 1
        }
}

server "{pub.,}tudorr.xyz" {
        listen on * port 80
        listen on :: port 80
        location * {
                block return 302 "https://$HTTP_HOST$REQUEST_URI"
        }
}

That’s it. I’m going to explain some parts of it.

		root "/htdocs/tudorr.xyz"

The first cool thing about httpd is that it runs in a chroot for that security that OpenBSD enforces. As such, all the paths are relative to the chroot. So the website is actually hosted on /var/www/htdocs/tudorr.xyz.

You can see that the first server block listens on port 81, on localhost. That’s because this is where the traffic from the reverse proxy ends up. You can also see that no server block listens to port 443. Again, it’s the job of the reverse proxy to handle HTTPS.

Acquire a certificate from Let’s Encrypt

Once again, OpenBSD comes pre-installed with a neat ACME client named… acme-client that does the job of retrieving certificates from Let’s Encrypt. Its config is also very simple:

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

domain tudorr.xyz {
        alternative names { pub.tudorr.xyz }
        domain key "/etc/ssl/private/tudorr.xyz.key"
        domain certificate "/etc/ssl/tudorr.xyz.crt"
        domain full chain certificate "/etc/ssl/tudorr.xyz.fullchain.pem"
        sign with letsencrypt
}

It really is self explanatory. This config actually comes by default with OpenBSD in /etc/acme-client.conf. The only changes I made were to change the domain from example.com to tudorr.xyz and add pub.tudorr.xyz as an alternative name. I also uncommented the last block, obviously. That’s it.

To get the cert, you only need to run as root:

acme-client -ADv domain.tld

For renewal, it’s the same command but without any flags. I added that to a daily cron. acme-client doesn’t attempt to update the certificates if they are not expired.

Setup the reverse proxy

This one also came bundled, its name is relayd. This thing is a beast, it operates on both the network layer, with the help of pf, and on the application layer either by DNS, HTTP or TCP (TCP proxy!!!). It does load balancing, proxying and redirection. And on top of that, it supports TLS.

table <local> { "127.0.0.1" }
table <pi> { "192.168.12.122" }

http protocol "httpproxy" {
        tls { no tlsv1.0, ciphers "HIGH" }
        return error
        pass request quick header "Host" value "tudorr.xyz" \
                forward to <local>
        pass request quick header "Host" value "pub.tudorr.xyz" \
                forward to <pi>
        block
}

relay "proxy" {
        listen on 0.0.0.0 port 443 tls
        protocol "httpproxy"

        forward to <local> port 81 mode loadbalance
        forward to <pi> port 2015 mode loadbalance
}

All it does is to match the website we want to access by the Host HTTP header and forwarding the traffic to a server. I can also add multiple IPs in either <local> or <pi> and they will be load balanced.

Also, a mild gotcha, is the path of the certificates. By default, the certificate is in /etc/ssl/<ip>.crt and the key is in /etc/ssl/private/<ip>.key. In my case, <ip> is 0.0.0.0. I just symlinked /etc/ssl/tudorr.xyz.fullchain.pem to 0.0.0.0.crt and /etc/ssl/private/tudorr.xyz.key to 0.0.0.0.key and it just worked.

Moving the data

Well, my website’s code in hosted on GitHub. Same for this blog. For both I cloned the repo from GitHub as a bare repository in the home directory on the OpenBSD server. In each bare repo I installed a post-receive hook like this one:

#!/bin/sh
GIT_WORK_TREE=/var/www/htdocs/tudorr.xyz git checkout -f

Really, no black magic. /var/www/htdocs/tudorr.xyz is a normal git repository that has its origin in the bare repo. When I want to update the site from my PC, I git push to the bare repository by SSH. After the push is done, the hook is executed and the website is updated.

I hope you will consider an OpenBSD-based solution in the future. Happy hacking!