Migrating zacbrown.org

For a while, this site was hosted using Ghost. I recently migrated the content to a new VPS that serves this site as static HTML. This involved building or configuring a few things I’d never done before. Given that I’ve mentioned the migration a few times in recent posts, I figured it was worth elaborating on. If nothing else, it’ll be a high level record of what I did to setup the site that I can refer to later.

In the Before Time…

This site has been hosted across a number of different service providers and blogging software. The list includes, but not limited to:

Now that I’m looking at the list, I’ve tried just about every popular option.

I decided to migrate off of Ghost most recently because I got tired of NPM’s “update 200 packages” every two days shenanigans. I see enough of these warnings at work and don’t really want to deal with them for my personal site.

On top of that, my site doesn’t change that often. It’s just the right use case for a static site generator.

The New Site


The new site is hosted on prmgr.com using OpenBSD. I chose OpenBSD for a number of reasons, but the briefest is that it’s simple1. The hosted site itself is static HTML, a basic CSS stylesheet, and font files for IBM Plex Sans and JetBrains Mono. I chose to host the fonts myself with the Cache-Control header set to 1 year - mostly because I’m not wild about Google’s services these days due to privacy concerns.

For hosting the site, I’m using relayd as a reverse proxy and httpd to serve the HTML content itself. I primarily have relayd in front to handle setting some standard headers like Cache-Control, Referrer-Policy, and others from https://securityheaders.com. It’s also responsible for handling TLS termination which is mostly handy for any other services I want to host off the server, besides the main site.

I use a bare bones configuration for httpd. It basically looks like:

# make all OpenBSD default mime types available
types {
  include "/usr/share/misc/mime.types"

# this is the main domain
server "www.zacbrown.org" {

  # listen on localhost port 8080 using IPv4 and IPv6
  listen on port 8080
  listen on ::1 port 8080

  # httpd chroots into /var/www
  # so the root directory is relative to that
  root "/htdocs/www.zacbrown.org"

  # make /var/www/acme availabe
  location "/.well-known/acme-challenge/*" {

    # /var/www/acme
    root "/acme"

    # remove .well-known and acme-challenge from the path
    # before looking for the file in /var/www/acme
    request strip 2
[... more configuration for other domains and subdomains ...]

The most notable piece of the httpd configuration is the location "/.well-known/acme-challenge/*" bit. This is for acquiring and and renewing the Let’s Encrypt TLS certificates for the site. There are some good resources for how to setup relayd, httpd, and acme-client here:

Static Site Generation

I’m comfortable writing HTML, but similar to XML, I think it’s better treated as a machine format. It’s precise in its structure, but the open and close tags make it too verbose to write prose in. Instead, MarkDown or similar text formats are a lot easier to write in. That choice necessitates some sort of post processing if I want to serve HTML to browsers.

This time around, I decided to write my own static site generator. I’ve always been frustrated with Hugo and Jekyll for how “big” they are. They’re general purpose solutions which have a lot of knobs. When I started out writing the tool, I had a clear idea of how I wanted the tool to work. The result was zsitegen. It’s written in Go and ~500 lines long. Its primary dependencies are github.com/gomarkdown/markdown and github.com/gorilla/feeds - two heavily used and battle tested packages. The rest of the code is scaffolding and parsing of some configuration files.

At some point, I’ll write a separate post about how zsitegen works. Briefly, it has:

The output of the tool can be copied in full to a server’s htdocs or www folder to serve it.


Deployment is simple - I use git for version control of the site’s content. Changes that hit the main branch are built and deployed to the zacbrown.org server. I use rsync over SSH to copy the generated site to the deployed server. I wanted something that was secure, reliable, and simple to debug.

I use a unique SSH key from the CI/CD infrastructure that deploys to the /var/www/htdocs/ directory using a specific user on the server. The user is chroot‘ed to prevent it from doing anything besides writing the website files.

Closing Thoughts

Something I didn’t cover was how I’m monitoring the server securely (Prometheus exporters + WireGuard for connectivity). I’d like to translate a lot of the setup into something like Ansible or similar going forward. That would help make redeploying to new instances faster. I do track configuration of most notable static files for my machines in a private git repo, but a real solution would be more robust.

Overall, I’m pretty happy with how the server is setup now.

Posted on 2021-04-04

  1. I could write a whole post about what I liked about the *BSD’s, but I’ll leave it at how stable the layout of the filesystem, configuration, and init scripts. The differences between OpenBSD 5.x and OpenBSD 6.8 are minimal when compared to Ubuntu 18.04 LTS versus Ubuntu 20.04 LTS.