Web server setup with OpenBSD

by Jochem Kossen

As a hobby, I host my website on my own server. It’s currently running the OpenBSD operating system. OpenBSD comes with batteries included. Among other things, it comes with a web (http / https) server, a transparent proxy and load balancer, a mail (SMTP) server, the OpenSSH shell server and a packet filter (“firewall”). Everything you need to host your website yourself. All nicely integrated. All for free.

OpenBSD is very consistent, lightweight and probably the most secure functional operating system available. Maintenance is a breeze, so it’s great for serving purposes.

Want to setup your own OpenBSD box? Then it can be helpful to see how other people have configured their machines.

Below you can find most of my configuration files.

By the way, I owe credit to Derek Sivers. While setting up OpenBSD and searching information about it I came across his website. He has quite some sensible things to say that rhyme with things I want to accomplish.

You might want to read his Tech Independence tutorial on how to set up your own server.

Doas

Doas is a replacement for sudo developed by the OpenBSD developers. It allows you to execute commands as another user. I’ve set it up to allow users in the wheel group to execute any command as root.

/etc/doas.conf

 1# $OpenBSD: doas.conf,v 1.1 2016/09/03 11:58:32 pirofti Exp $
 2# Configuration sample file for doas(1).
 3# See doas.conf(5) for syntax and examples.
 4
 5# Non-exhaustive list of variables needed to build release(8) and ports(7)
 6#permit nopass setenv { \
 7#    FTPMODE PKG_CACHE PKG_PATH SM_PATH SSH_AUTH_SOCK \
 8#    DESTDIR DISTDIR FETCH_CMD FLAVOR GROUP MAKE MAKECONF \
 9#    MULTI_PACKAGES NOMAN OKAY_FILES OWNER PKG_DBDIR \
10#    PKG_DESTDIR PKG_TMPDIR PORTSDIR RELEASEDIR SHARED_ONLY \
11#    SUBPACKAGE WRKOBJDIR SUDO_PORT_V1 } :wsrc
12
13# Allow wheel by default
14permit persist keepenv :wheel

Web

OpenBSD includes acme-client which I use to create and manage my SSL certificates from Let’s Encrypt. ACME stands for “Automatic Certificate Management Environment”.

For the Web server I use OpenBSD’s included httpd. It works fine for serving plain html. For more complex needs it supports the FastCGI protocol.

I also use relayd as a proxy server. It allows me to (among other things) add CSP (Content Security Policy).

acme-client

Certificate configuration

/etc/acme-client.conf:

 1#
 2# $OpenBSD: acme-client.conf,v 1.4 2020/09/17 09:13:06 florian Exp $
 3#
 4authority letsencrypt {
 5	api url "https://acme-v02.api.letsencrypt.org/directory"
 6	account key "/etc/acme/letsencrypt-privkey.pem"
 7}
 8
 9authority letsencrypt-staging {
10	api url "https://acme-staging-v02.api.letsencrypt.org/directory"
11	account key "/etc/acme/letsencrypt-staging-privkey.pem"
12}
13
14domain jkossen.nl {
15	alternative names { www.jkossen.nl }
16	domain key "/etc/ssl/private/jkossen.nl.key"
17	domain full chain certificate "/etc/ssl/jkossen.nl.fullchain.pem"
18	sign with letsencrypt
19}

To request your certificate, first set up httpd and relayd as below since it serves the acme challenge, then execute:

1$ doas acme-client -v jkossen.nl

Check certificates daily

/etc/daily.local:

1next_part "Refreshing Let's Encrypt certificates:"
2
3acme-client jkossen.nl && rcctl reload relayd

TODO: There’s a good reason to do this, but I forgot …

1$ cd /etc/ssl
2$ doas ln -s jkossen.nl.fullchain.pem jkossen.nl.crt

httpd

/etc/httpd/httpd.conf

 1# $OpenBSD: httpd.conf,v 1.22 2020/11/04 10:34:18 denis Exp $
 2
 3#
 4# jkossen.nl
 5#
 6server "jkossen.nl" {
 7	listen on 127.0.0.1 port 8080
 8	root "/jkossen.nl/public"
 9	log style forwarded
10	location "/.well-known/acme-challenge/*" {
11		root "/acme"
12		request strip 2
13	}
14	location match "/posts/(.*)" {
15		block return 301 "https://$HTTP_HOST/%1"
16	}
17	location "/*" {
18		directory auto index
19	}
20}
21
22server "jkossen.nl" {
23	alias "www.jkossen.nl"
24	listen on * port 80
25	location "/.well-known/acme-challenge/*" {
26		root "/acme"
27		request strip 2
28	}
29	location "/*" {
30		block return 301 "https://$HTTP_HOST$REQUEST_URI"
31	}
32}
33
34server "www.jkossen.nl" {
35	listen on 127.0.0.1 port 8080
36	log style forwarded
37	block return 301 "https://jkossen.nl$REQUEST_URI"
38}
39
40types {
41	include "/usr/share/misc/mime.types"
42
43	# by default httpd serves ascii, not utf-8
44	"application"/"rss+xml; charset=utf-8"			rss
45	"text"/"html; charset=utf-8"				html htm shtml
46	"text"/"plain; charset=utf-8"				txt
47	"text"/"xml; charset=utf-8"				xml
48	"text"/"markdown; charset=utf-8"			md
49}

relayd

/etc/relayd/relayd.conf

 1ipv4="192.168.2.10"
 2ipv6="2a02:a442:243a:1:221:86ff:fe9e:c2c4"
 3
 4table <local> { 127.0.0.1 }
 5
 6http protocol https {
 7	tls ciphers "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256"
 8
 9	tls keypair "jkossen.nl"
10
11	match request header append "X-Forwarded-For" value "$REMOTE_ADDR"
12	match request header append "X-Forwarded-Port" value "$REMOTE_PORT"
13
14	match response header set "Referrer-Policy" value "same-origin"
15	match response header set "X-Frame-Options" value "deny"
16	match response header set "X-XSS-Protection" value "1; mode=block"
17	match response header set "X-Content-Type-Options" value "nosniff"
18	match response header set "Strict-Transport-Security" value "max-age=31536000; includeSubDomains; preload"
19	match response header set "Content-Security-Policy" value "default-src 'self'"
20	match response header set "Permissions-Policy" value "accelerometer=()"
21	match response header set "Cache-Control" value "max-age=86400"
22
23	return error
24	pass
25}
26
27relay wwwtls {
28	listen on $ipv4 port 443 tls
29	protocol https
30	forward to <local> port 8080
31}
32
33relay www6tls {
34	listen on $ipv6 port 443 tls
35	protocol https
36	forward to <local> port 8080
37}

E-mail

I use mailbox.org for my e-mail needs, and have OpenBSD set up to relay mail through them.

You don’t need to install packages. OpenBSD comes with OpenSMTPD installed.

/etc/mail/smtpd.conf

 1#	$OpenBSD: smtpd.conf,v 1.14 2019/11/26 20:14:38 gilles Exp $
 2
 3# This is the smtpd server system-wide configuration file.
 4# See smtpd.conf(5) for more information.
 5
 6table aliases file:/etc/mail/aliases
 7table secrets file:/etc/mail/secrets
 8
 9listen on socket
10
11# To accept external mail, replace with: listen on all
12#
13listen on lo0
14
15action "local_mail" mbox alias <aliases>
16action "mailbox" relay host smtps://account1@smtp.mailbox.org auth <secrets>
17#action "outbound" relay
18
19# Uncomment the following to accept external mail for domain "example.org"
20#
21# match from any for domain "example.org" action "local_mail"
22match from local for local action "local_mail"
23#match mail-from "@jkossen.nl" for any action "mailbox"
24match from local for any action "mailbox"
25#match from local for any action "outbound"

/etc/mail/secrets

Create this file containing your mailbox.org credentials.

1account1 me@mydomain.com:myPa$$w0rd

Replace me@mydomain.com with your mailbox.org user name (which should be your full e-mail address). Replace myPa$$w0rd with your password.

NOTE: account1 in smtps://account1@smtp.mailbox.org in smtpd.conf refers to account1 in /etc/mail/secrets.

If you change it, change both.

Set permissions

Make sure only root and smtpd can read your account information and password.

1$ doas chown root:_smtpd /etc/mail/secrets
2$ doas chmod 0640 /etc/mail/secrets