httpd.rocks


Interested in using HAProxy instead of OpenBSD’s relayd? Then keep reading!


Combine CERTs and KEYs

HAProxy requires your acme-generated CRT and KEY files to be combined into a single PEM.

First create the directory we will need to contain the files be plan to generate:

doas mkdir /etc/ssl/certs
doas chmod 644 /etc/ssl/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"
CERTS_OUTPUT_DIR="/etc/ssl/certs"

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

# Combine .fullchain.pem and .key into a single .pem file in /etc/ssl/certs
echo "Combining .fullchain.pem and .key files for each domain..."
for DOMAIN in $DOMAINS; do
    CERT="/etc/ssl/$DOMAIN.cert"
    KEY="/etc/ssl/private/$DOMAIN.key"
    COMBINED_PEM="$CERTS_OUTPUT_DIR/$DOMAIN.pem"

    if [ -f "$CERT" ] && [ -f "$KEY" ]; then
        doas sh -c "cat '$CERT' '$KEY' > '$COMBINED_PEM'"
        doas chmod 644 "$COMBINED_PEM"
        echo "Combined $CERT and $KEY into $COMBINED_PEM"
    else
        echo "Missing $CERT or $KEY for $DOMAIN, skipping."
    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:

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.

This will check if you need to renew certificates every day at midnight (server time). If new certs are needed, it will properly combine the generated crt and key files into a single <project-name>.pem file under the shared directory /etc/ssl/certs.

Since we haven’t run this script yet, we should execute it for building the initial pem files required for HAProxy:

doas sh /path/to/renew_certs.sh

Setting Up HAProxy

First, install the package:

doas pkg_add haproxy

Now configure the core /etc/haproxy/haproxy.cfg (take note of the extension! OpenBSD uses cfg rather than the standard conf for HAProxy) and add the following to the existing file:

defaults
    log     global
    mode    http
    option  httplog
    option  dontlognull
    timeout connect 5000ms
    timeout client  50000ms
    timeout server  50000ms

frontend http_in
    bind *:80
    redirect scheme https if !{ ssl_fc }
    default_backend main_backend

frontend https_in
    bind *:443 ssl crt /etc/ssl/certs/
    default_backend main_backend

# Backend to httpd with security headers, no TLS
backend main_backend
    http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains"
    http-response set-header X-Content-Type-Options "nosniff"
    http-response set-header X-Frame-Options "DENY"
    http-response set-header X-XSS-Protection "1; mode=block"
    http-response set-header Content-Security-Policy "script-src 'self'"
    http-response set-header Referrer-Policy "no-referrer"
    http-response set-header Permissions-Policy "microphone=()"
    server local_httpd 127.0.0.1:8080

The haproxy.cfg in a nutshell:

frontend http_in:
redirects all HTTP requests to HTTPS
uses main_backend to set security headers
frontend https_in:
uses main_backend to set security headers
backend main_backend:
sets sane security header defaults (change these as you please)

Important: Take note of the line:

bind *:443 ssl crt /etc/ssl/certs/

This tells HAProxy to dynamically scan a directory containing our certificates. We set this up previously with our automated renew_certs.sh script.

This is handy if you decide to host multiple sites on a single server. Otherwise, you would have to edit and reload your HAProxy config everytime you setup a new website.

Once that’s complete, test that everything is working, and if so, enable and start HAProxy:

doas haproxy -c -f /etc/haproxy/haproxy.cfg
doas rcctl enable haproxy
doas rcctl start haproxy

Forward Traffic to Non-WWW

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

It’s Alive!

Now check out your website! Everything should work as intended.

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!


Contribute

I’m far from an OpenBSD expert! Please help improve this project!