Philipp's Blog

OpenBSD server: web server

In my last post I wrote about how to do a basic install of OpenBSD. This post will be about how to configure the httpd web server and obtain HTTPS certificates with acme-client.


The following assumes port 80 and 443 are allowed in the firewall, as configured in Part 1.


Configure httpd

Create the web directory:

doas mkdir -p /var/www/htdocs/domain.net

Create an example web page in /var/www/htdocs/domain.net/index.html:

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title>domain.net</title>
  </head>
  <body>
    <h1>domain.net</h1>
    <p>Hello from OpenBSD.</p>
  </body>
</html>

Edit the httpd configuration in /etc/httpd.conf

server "domain.net" {
    listen on * port 80
    root "/htdocs/domain.net"
}

server "www.domain.net" {
    listen on * port 80
    block return 301 "http://domain.net$REQUEST_URI"
}

Check the configuration, enable and start the httpd server:

doas httpd -n
doas rcctl enable httpd
doas rcctl start httpd

You now have a basic website running on port 80 (HTTP). In the next step we are going to configure acme-client to request an HTTPS certificate from Let’s Encrypt.

Configure acme-client

Create the ACME challenge directory:

doas mkdir -p /var/www/acme

Create /etc/acme-client.conf:

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

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

Add the acme-challenge configuration in /etc/httpd.conf:

server "domain.net" {
    listen on * port 80
    root "/htdocs/domain.net"

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

server "www.domain.net" {
    listen on * port 80
    root "/htdocs/domain.net"

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

Test the configuration and restart httpd:

doas httpd -n
doas rcctl restart httpd

Before requesting the certificate, make sure domain.net and www.domain.net points to your servers IP address.


Request the certificate:

doas acme-client -v domain.net

Update /etc/httpd.conf with the SSL settings:

server "domain.net" {
    listen on * port 80

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

    location * {
        block return 301 "https://domain.net$REQUEST_URI"
    }
}

server "www.domain.net" {
    listen on * port 80

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

    location * {
        block return 301 "https://domain.net$REQUEST_URI"
    }
}

server "domain.net" {
    listen on * tls port 443
    root "/htdocs/domain.net"

    tls {
        certificate "/etc/ssl/domain.net.fullchain.pem"
        key "/etc/ssl/private/domain.net.key"
    }
}

server "www.domain.net" {
    listen on * tls port 443

    tls {
        certificate "/etc/ssl/domain.net.fullchain.pem"
        key "/etc/ssl/private/domain.net.key"
    }

    block return 301 "https://domain.net$REQUEST_URI"
}

Test the configuration and restart httpd:

doas httpd -n
doas rcctl restart httpd

Set up auto-renewal

Set up auto-renewal with crontab by editing with doas crontab -e, and adding:

~ 3 * * * acme-client domain.net && rcctl reload httpd

The ~ randomizes the minute.

BONUS: How I deploy my website

My website is built with Hugo. I have a sync script in the website directory on my local machine that looks like this:

#!/bin/sh
set -e

hugo
rsync -rlptDzv --delete public/ ph@kullbach.net:/home/ph/deploy/kullbach.net/
ssh ph@kullbach.net 'doas rsync -a --delete /home/ph/deploy/kullbach.net/ /var/www/htdocs/kullbach.net/'

This script builds the website locally, and syncs it to my server. After that it runs another rsync on the server that copies the files into the httpd web directory.

To make this work without me having to type the password for doas, I added a nopass rule to /etc/doas.conf:

permit nopass ph as root cmd rsync args -a --delete /home/ph/deploy/kullbach.net/ /var/www/htdocs/kullbach.net/

It is important to make the nopass rules as specific as possible.


To make this totally seamless you could add your SSH key to an ssh-agent on your local machine, so you do not have to type your SSH key password every time.

Wrapping Up

That’s part 2 of this series finished.


You can now check out Part 2.5 of this series.