Sep 19, 2020

Last reviewed: Feb, 2021

artnoi.com is now on OpenBSD!

After a few days on OpenBSD, I have made my mind to move the webserver from Digitalocean FreeBSD VPS (pre-installed on ZFS) to my custom OpenBSD installation on Vultr (Here is a random guide I found on Google for encrypted installation).

The process took me ~30 minutes from spinning up a fresh VM to when everything including HTTPS is ready. Because Poudriere is no longer needed, ports.artnoi.com is no longer available.

Why OpenBSD?

OpenBSD’s base comes complete with all the software required to build a secure and simple webserver, namely:

httpd vs NGINX

TLDR: NGINX is used only as my HTTPS front-end for the clients to establish secure connections.

Although I am very impressed with NGINX’s webserver performance, I find OpenBSD’s httpd(8) to be just good enough to serve a mostly static text website, so I switched my main webserver from NGINX to httpd(8).

Then I found out that it is currently impossible to change error page on OpenBSD http(8), but that can be mitigated with proxies.

I do however continue to use NGINX to serve bloated web pages, and as relays (proxies) for my HTTPS front-end. I tried OpenBSD’s relayd(8) as internet-facing reverse proxy. But because I’m a noob, my relay.conf(5) had some minor with TLS, where it seemed my server did not serve all the certificates needed. This was very small, and barely noticable that desktop users won’t even notice this, since the incomplete handshake was acceptable to Firefox and Chrome (both of which I believe have root certficates that could actually verify my incomplete certificate).

So I have to use NGINX as my internet-facing reverse proxy for now, though I still put OpenBSD relayd(8) behind NGINX to actually proxy connection to the actual webservers, filter incoming HTTP headers, and actively load-balance the hosts through Wireguard tunnel.

Configuring httpd(8)

Simple httpd.conf(5)

This configuration is for serving simple HTTP website on non-standard port 8080

## GLOBAL configuration

httpd_ip = "127.0.0.1"

# Content types
types {

  # uncomment 'include' line below to use all types
  # include "/usr/share/misc/mime.types"
  application/pdf pdf
  image/png       png
  image/svg+xml   svg ico
  text/css        css
  text/html       html htm
  text/plain      txt

}

## Virtual servers
server "artnoi.com" {
  alias "www.artnoi.com"
  listen on $httpd_ip port 8080
  root "/htdocs/html-artnoi.com"
}

Check httpd(8) configuration /etc/httpd.conf with:

httpd -n -f /etc/httpd.conf;

If you want to run httpd(8) as daemon, configure rc(8).

HTTP with TLS using httpd(8) and acme-client(1)

We can configure HTTPS on OpenBSD http(8) with out any external packages. There is an article on OpenBSD Handbook

services/webserver/ssl) on how to do this. In short, you first set up httpd(8) to be ready to handle ACME challenges via simple port 80 HTTP. (OpenBSD ships with a production-ready ACME client acme-client(1)! How about that!?).

Configuring acme-client(1)

We will be using production server here

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

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

Configuring httpd(8) for ACME challenges

Now, configure httpd(8) to properly handle ACME challenge connection when we later run acme-client(1). You should read how Let’s Encrypt ACME challenge works.

Only server directive (block) is shown

server "artnoi.com" {
  alias "www.artnoi.com"
  listen on $httpd_ip port 80
  root "/htdocs/html-artnoi.com"

  # acme challenge
  location "/.well-known/acme-challenge/$ext_if" {
    root "/acme"
    request strip 2
  }
}

Obtaining Let’s Encrypt Certificate

After you are done configuring acme-client.conf(5) and httpd.conf(5), use acme-client(1) to get challenge the our webserver and obtain Let’s Encrypt certificates on OpenBSD:

# acme-client -v artnoi.com;

Based on our acme-client.conf(5), our certificates should verify both artnoi.com and www.artnoi.com.

If you fail, recheck pf.conf(5), httpd.conf(5), the DNS records for your domains, and the permission of /var/www/htdocs/.well_known.

Enabling HTTP with TLS on httpd(8) (with redirection to HTTPS)

We now need 2 servers in httpd.conf(5), first is the main server listening on 443 for HTTPS, the other listens on port 80 and is specifically for redirecting incoming HTTP to HTTPS.

Only server directives (blocks) are shown, and note the tls option on HTTPS listen line.

server "artnoi.com" {
  alias "www.artnoi.com"
  listen on $httpd_ip tls port 443

  tls {
    certificate "/etc/ssl/artnoi.com.fullchain.pem"
    key "/etc/ssl/private/artnoi.com.key"
  }

  root "/htdocs/html-artnoi.com"

  # acme challenge
  location "/.well-known/acme-challenge/$ext_if" {
    root "/acme"
    request strip 2
  }

}

server "artnoi.com" {
  alias "www.artnoi.com"
  listen on $httpd_ip port 80

  # redirect to https
  block return 301 "https://artnoi.com$REQUEST_URI"
}

Right now, httpd(8) should spawn 2 virtual servers, one on port 443 for HTTPS, and one on port 80 for HTTP. The one on port 80 will actually redirect to HTTPS port 443, if the request location is not .well-known, which is challenged by ACME certificate servers.

You should be able to renew the certificates without having to change configuratiom, i.e. bring down your small website.

Enjoy!


Back to top