Setup an HTTPS-enabled web server with httpd
on OpenBSD. Includes A+ security report configuration with relayd
or haproxy
.
If you’d prefer to use something like Caddy instead, check out caddy.ninja.
This guide assumes you have already setup OpenBSD on your desired server of choice. If you need help setting up OpenBSD on a VPS, check out this helpful guide: Installing OpenBSD on Linveo KVM VPS
Most commands will need to run via doas
, since you should be logged in as a created user - never root
directly.
All the examples in this guide use httpd.rocks
for the domains (how meta…). Please remember to change this to your desired URL.
Make sure your DNS records are setup and working as intended with your desired domain. You can check their status with:
dig httpd.rocks
Before doing anything else, you need to make sure your /etc/pf.conf
is allowing traffic on ports 80
and 443
. You should also include sane defaults for limiting connections (in this example, we are basing these limits off a VPS with 4GB RAM / 4 cores CPU).
Make sure you include the following:
pass in on any proto tcp from any to any port {80 443} keep state \
(max-src-conn 100, max-src-conn-rate 30/5, overload <block_table> flush global)
pass out on any proto tcp from any to any port {80 443} keep state
table <block_table> persist
block in quick from <block_table>
# Connection limits
set limit states 100000 # Maximum state table entries
set timeout interval 10 # Interval between state purges
set optimization "normal" # General optimization for connection tracking
Once that is saved, simply reload the ruleset:
pfctl -f /etc/pf.conf
Make initial website folder and files:
doas mkdir -p /var/www/htdocs/httpd.rocks
Place your website files into this new folder and set proper permissions:
doas chmod -R 755 /var/www/htdocs/httpd.rocks
doas chown -R www:www /var/www/htdocs/httpd.rocks
Create the initial /etc/httpd.conf
file:
server "www.httpd.rocks" {
listen on * port 80
root "/htdocs/httpd.rocks"
location "/.well-known/acme-challenge/*" {
root "/acme"
request strip 2
}
}
server "httpd.rocks" {
listen on * port 80
root "/htdocs/httpd.rocks"
location "/.well-known/acme-challenge/*" {
root "/acme"
request strip 2
}
}
Then get httpd
up and running:
doas rcctl start httpd
Note: If you encounter runtime errors with httpd
, you might be required to add the following to your /etc/rc.conf.local
file:
httpd_flags=""
If everything was setup properly, you should be able to visit the HTTP-only version of your website online. The only problem is HTTPS isn’t setup…
..yet!
Before anything else, we need to create proper directories for acme-client
(our next steps) and set their permissions:
doas mkdir -p -m 750 /etc/ssl/private
doas mkdir -p -m 755 /var/www/acme
Create the /etc/acme-client.conf
file and include the following:
authority letsencrypt {
api url "https://acme-v02.api.letsencrypt.org/directory"
account key "/etc/acme/letsencrypt-privkey.pem"
}
domain httpd.rocks {
alternative names { www.httpd.rocks }
domain key "/etc/ssl/private/httpd.rocks.key"
domain full chain certificate "/etc/ssl/httpd.rocks.crt"
sign with letsencrypt
}
Now we can run the core acme-client
command to generate our certificates:
doas acme-client -v httpd.rocks
If everything goes smoothly, your new certificates should be generated and issued. The next thing you will want to do is automatically check for expired certs.
Then create a separate script (this will be helpful if you plan to host multiple sites on a single server). Name it something like renew_certs.sh
and save it under a local directory (ie. /home/username/scripts
):
#!/bin/sh
DOMAINS="httpd.rocks example1.com example2.com example3.com"
echo "Checking certificates for each domain..."
# Loop through each domain and run acme-client only if renewal is needed
for DOMAIN in $DOMAINS; do
if ! doas acme-client -n "$DOMAIN"; then
echo "Certificate for $DOMAIN needs renewal, running acme-client..."
doas acme-client "$DOMAIN" || exit 1
else
echo "Certificate for $DOMAIN is still valid, skipping renewal."
fi
done
# Reload httpd after updating certificates
echo "Reloading httpd..."
doas rcctl reload httpd
echo "httpd reloaded successfully."
For reference I have included multiple domains if you decide to host several websites through one server. Remove these if you only plan to host a single domain.
Set executable permissions on this script:
doas chmod +x /path/to/renew_certs.sh
Then setup the following cronjob
by running crontab -e
and entering in:
0 0 * * * doas -u <user> /path/to/renew_certs.sh
Replace <user>
with your username. Certificate automation has been completed!
Next we will setup security headers and HTTPS redirection with OpenBSD’s built-in relayd
.
Note: If you would prefer to use HAProxy instead, follow this guide here
You will want to create a new file located at /etc/relayd.conf
:
ip4="YOUR IPv4"
ip6="YOUR IPv6"
table <www> { 127.0.0.1 }
log connection
http protocol https {
match request header append "X-Forwarded-For" value "$REMOTE_ADDR"
match request header append "X-Forwarded-By" \
value "$SERVER_ADDR:$SERVER_PORT"
match request header set "Connection" value "close"
tcp { sack, backlog 128 }
tls { keypair httpd.rocks }
match request header "Host" value "httpd.rocks" forward to <www>
# Add security headers
match response header append "Strict-Transport-Security" value "max-age=31536000; includeSubDomains; preload"
match response header append "Content-Security-Policy" value "default-src 'self'; script-src 'self'; object-src 'none';"
match response header append "X-Content-Type-Options" value "nosniff"
match response header append "X-Frame-Options" value "DENY"
match response header append "Referrer-Policy" value "no-referrer"
match response header append "Permissions-Policy" value "geolocation=(), microphone=(), camera=()"
}
relay wwwtls {
listen on $ip4 port 443 tls
protocol https
forward to <www> port 80 check icmp
}
relay www6tls {
listen on $ip6 port 443 tls
protocol https
forward to <www> port 80 check icmp
}
Make sure you edit your IPv4
and IPv6
with your server IPs. Then replace all instances of httpd.rocks
with your own domain. The security headers are set to my own personal preference - feel free to change these as you see fit! Test the config with the following command:
doas relayd -n
If configuration passes, we just need to enable relayd
on boot and start it up:
doas rcctl enable relayd
doas rcctl start relayd
Now HTTPS will be working as intended!
Return to the core /etc/httpd.conf
file and add the following redirect block to your www
server section:
server "www.httpd.rocks" {
listen on * port 80
root "/htdocs/httpd.rocks"
block return 301 "https://httpd.rocks$REQUEST_URI"
location "/.well-known/acme-challenge/*" {
root "/acme"
request strip 2
}
}
Restart your httpd server again:
rcctl restart httpd
Now check out your website! Everything should work perfectly!
You should have valid TLS, your standard HTTP request should forward to HTTPS, all www
requests should forward to non-www
, and your security headers should score an A+.
That’s it!
Please refer to these additional (and mostly better) resources, books, and documentation:
I’m far from an OpenBSD expert! Please help improve this project!