Using relayd, httpd, pf and acme-client - the openbsd stack
If you need to deploy a static site to your OpenBSD, and if you have
relayd and httpd as your stack, then this post lists the config and
commands needed to get the website up and running.
No wait. I could care less about you. This post is for me in case I
forget things about my websites and servers. It may or may not help you.
Spend more time reading at your own risk.
Domain Setup
Point the new domain to your server’s IP.
It can be done in 2 ways:
1) The My Way
It’s by changing the domain’s nameservers to point to that of the
server’s hosting provider, and then managing the DNS from the
latter.
If my domain is in NameCheap.com and my server is in Vultr.com, then
I’d go to NameCheap.com and change my domain’s nameservers to these:
ns1.vultr.com
ns2.vultr.com
And then I’d go to the DNS section of Vultr.com and then “Add a New
Domain”. It’ll ask to link the domain to a server that’s already there.
Do it.
Then it’ll automatically create an A record for the domain pointing
to the server’s IP. You can then create other DNS records for the domain
from here as well. I create my CNAMEs here. I don’t use the wildcard
CNAME record. I create one for each subdomain I want, explicitly.
Using this way means I can see the link between the domains and the
servers closely.
2) The Other Way
In NameCheap.com, create an A record for the domain pointing to the
server’s IP. And create the CNAME’s too there if you need them. That’s
it. This looks easy, but I buy domains from many places depending on the
price, and then use vultr od DigitalOcean only for servers. So getting
used to a single interface to do DNS things saves time.
Relayd Setup
I use relayd
to listen to traffic from the external
world and then point them to the httpd
server process
that’s running at localhost
. Here’s the relayd setup.
The /etc/relayd.conf
file:
log state changes
log connection errors
prefork 5
table <httpd> { 127.0.0.1 }
http protocol "https" {
tls keypair "example.com"
return error
match request header set "X-Forwarded-For" value "$REMOTE_ADDR"
match request header set "X-Forwarded-By" value "$SERVER_ADDR:$SERVER_PORT"
# test in https://securityheaders.com
match response header remove "Server"
match response header append "Strict-Transport-Security" value "max-age=31536000"
match response header append "X-Frame-Options" value "SAMEORIGIN"
match response header append "X-XSS-Protection" value "1; mode=block"
match response header append "X-Content-Type-Options" value "nosniff"
match response header append "Referrer-Policy" value "strict-origin"
match response header append "Content-Security-Policy" value "default-src https: 'unsafe-inline'"
match response header append "Permissions-Policy" value "accelerometer=(none), camera=(none), geolocation=(none), gyroscope=(none), magnetometer=(none), microphone=(none), payment=(none), usb=(none)"
pass request quick header "Host" value "example.com" forward to <httpd>
}
relay "https" {
listen on 0.0.0.0 port 443 tls
protocol https
forward to <httpd> port 8080
}
What it does? - listens on the external interface on the ssl port for
incoming traffic - tells relayd to look for the tls private key and
certificate at the standard location (/etc/ssl
) and the
filenames being that of the domain’s name. - sets some standard security
headers in the response - forwards the request to the httpd
server process listening on port 8080 (we’ll see it configured that way
below)
Httpd Setup
Here’s /etc/httpd.conf
:
prefork 5
types {
include "/usr/share/misc/mime.types"
}
server "default" {
listen on 127.0.0.1 port 8080
block drop
}
include "/etc/httpd.d/example.com.conf"
And here’s /etc/httpd.d/example.com.conf
:
server "example.com" {
listen on * port 80
location "/.well-known/acme-challenge/*" {
root "/acme"
request strip 2
}
location * {
block return 301 "https://$HTTP_HOST$REQUEST_URI"
}
}
server "example.com" {
listen on 127.0.0.1 port 8080
root "/htdocs/example.com"
gzip-static
default type text/html
}
What it does? - creates a server process that’s listening for traffic
only from localhost on port 80800 - forces http requests to come back as
https
NOTE: Make sure the /var/www/htdocs/example.com
directory is created and chown’ed to your user, and chmod’ed to 755.
HTTPS Certificate
management with acme-client
Here’s /etc/acme-client.conf
:
authority letsencrypt {
api url "https://acme-v02.api.letsencrypt.org/directory"
account key "/etc/acme/letsencrypt-privkey.pem"
}
authority letsencrypt-staging {
api url "https://acme-staging-v02.api.letsencrypt.org/directory"
account key "/etc/acme/letsencrypt-staging-privkey.pem"
}
include "/etc/acme-client.d/example.com.conf"
And here’s /etc/acme-client.d/example.com.conf
:
domain example.com {
alternative names { www.example.com }
domain key "/etc/ssl/private/example.com:443.key"
domain full chain certificate "/etc/ssl/example.com:443.crt"
sign with letsencrypt
}
Automatic Cert Renewal
Management
Now you need to create new certificate for the domain with Let’s
Encrypt.
Here’s the command:
acme-client example.com
To renew it automatically, put this in a script and add to root’s
crontab to run it weekly.
Here’s the script at /usr/local/sbin/cert-renew
:
#!/bin/sh
acme-client example.com
rcctl restart relayd
And here’s the line you need to put into root’s crontab:
11 3 * * 5 /usr/local/sbin/cert-renew
Add this to cron using crontab -e
. This will run the
renewal script every Friday at 3:11 am.
Firewall management with PF
Make sure only ports 22 (ssh), 80 (http), 443 (https) are open for
traffic and all others are closed.
Here’s /etc/pf.conf
:
# network interface
if="vio0"
# skip filtering on loopback interface. It's trusted.
set skip on lo
# block all incoming traffic
block drop all
# allow outgoing traffic only on vio0
pass out on $if
# This rule allows incoming TCP traffic to the vio0 interface on specific ports (22, 80, 443).
# The flags S/SA part means that it will only pass packets with the SYN or SYN-ACK flags set, which are part of the TCP handshake process.
# The keep state part means that PF will keep track of the state of the connections and allow related packets through without needing additional rules.
# 22: SSH (Secure Shell)
# 80: HTTP (Hypertext Transfer Protocol)
# 443: HTTPS (Hypertext Transfer Protocol Secure)
pass quick proto tcp from any to $if port { 22, 80, 443 } flags S/SA keep state
Restart the services
Restart relayd and httpd with:
doas rcctl restart relayd httpd
Congrats on the new website!