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.