httpd.rocks


Setup an HTTPS-enabled web server with httpd on OpenBSD.

Includes optional configuration for use with relayd or haproxy.

If you’d prefer to use something like Caddy instead, check out caddy.ninja.


Before You Begin…

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.

Prep Your Domain(s)

Make sure your DNS records are setup and working as intended with your desired domain. You can check their status with:

dig httpd.rocks

Configure Firewall (pf.conf)

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

Setting Up httpd

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 "httpd.rocks" {
    listen on * port 80
    root "/htdocs/httpd.rocks"
}

Check that the configuration has no errors, then get httpd up and running:

doas httpd -n
doas rcctl start httpd

Note: If you do 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!

Configure acme-client.conf

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!

Configuring HTTPS

Next we will serve our site(s) through HTTPS. For most static sites or personal blogs, using the built-in TLS support from httpd should be perfectly fine. If you have a need for custom security headers or a transparent proxy take look at the additional guides below.

Using relayd or haproxy (optional)

But for our needs we will continue with using base httpd.

Tweaking httpd.conf

Return to your previous httpd.conf and include the following changes:

server "httpd.rocks" {
    alias "www.httpd.rocks"
    listen on * tls port 443
    root "/htdocs/httpd.rocks"
    hsts

    location "/.well-known/acme-challenge/*" {
        root "/acme"
        request strip 2
    }
    
    tls {
        certificate "/etc/ssl/httpd.rocks.crt"
        key "/etc/ssl/private/httpd.rocks.key"
    }
}
server "httpd.rocks" {
    alias "www.httpd.rocks"
    listen on * port 80

    location "/.well-known/acme-challenge/*" {
        root "/acme"
        request strip 2
    }

    block return 301 "https://$SERVER_NAME$REQUEST_URI"
}

The changes are fairly straightforward. The alias saves us the trouble of creating a separate server block just for www targets. We also now include our acme-generated certificates inside the server block that handles traffic through port 443 and simply redirect all standard HTTP traffic (port 80) to that block. The included hsts tells the browser to use HTTPS by default, so we can avoid extra unnecessary redirects.

The .well-known directory location is required for when we need to renew our SSL certificates (based off the renew script above).

Now restart the service:

doas rcctl restart httpd

Redirecting www Traffic

Now we are in the final stretch. Everything should be up-and-running, except you are now serving both www and non-www as independent entities. This is fine, but you should normally stick to one variation. You can edit your httpd.conf with the changes below in order to setup automatic redirects of www traffic to non-www:

server "httpd.rocks" {
    listen on * tls port 443
    root "/htdocs/httpd.rocks"
    hsts

    location "/.well-known/acme-challenge/*" {
        root "/acme"
        request strip 2
    }
    
    tls {
        certificate "/etc/ssl/httpd.rocks.crt"
        key "/etc/ssl/private/httpd.rocks.key"
    }
}
server "httpd.rocks" {
    listen on * port 80

    location "/.well-known/acme-challenge/*" {
        root "/acme"
        request strip 2
    }

    block return 301 "https://$SERVER_NAME$REQUEST_URI"
}
server "www.httpd.rocks" {
    listen on * port 80
    listen on * tls port 443

    location "/.well-known/acme-challenge/*" {
        root "/acme"
        request strip 2
    }
    
    tls {
        certificate "/etc/ssl/httpd.rocks.crt"
        key "/etc/ssl/private/httpd.rocks.key"
    }
    
    block return 301 "https://$SERVER_NAME$REQUEST_URI"
}

We remove the original alias parameter and create an additional server block (www focused). Pretty simple stuff.

Once again, restart the service:

doas rcctl restart httpd

It’s Alive!

Now check out your website! Everything should work perfectly!

You should have valid TLS, your standard HTTP request should forward to HTTPS, and all www requests should forward to non-www.

That’s it!


Extras

HTTP Basic Authentication

OpenBSD ships with the built-in htpasswd for configuring user authenicated sites with httpd.

Important: it is best practice to always serve basic authentication logins over HTTPS-only.

First, create the main httpd.passwd file and associated user:

htpasswd /var/www/etc/httpd.passwd youruser

Also make sure to give this file proper permissions:

doas chown www /var/www/etc/httpd.passwd
doas chmod 600 /var/www/etc/httpd.passwd

Then enter a strong password once prompted. Next we will need to tweak our httpd.conf files. In the example below, we plan to password protect the secure.html file on httpd.rocks:

server "httpd.rocks" {
    listen on * tls port 443
    root "/htdocs/httpd.rocks"
    hsts

    location "/.well-known/acme-challenge/*" {
        root "/acme"
        request strip 2
    }

    location "/secure.html" {
        authenticate "Restricted Area" with "/etc/httpd.passwd"
    }
    
    tls {
        certificate "/etc/ssl/httpd.rocks.crt"
        key "/etc/ssl/private/httpd.rocks.key"
    }
}

Then reload httpd and you’re done:

rcctl restart httpd

Now navigating to your secure.html page on your website will prompt for a username and password. You can test it yourself here: httpd.rocks/secure.html (login: dog password: cat)

References

Please refer to these additional (and mostly better) resources, books, and documentation:


Contribute

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