Let's Encrypt and OpenBSD's httpd

June 2020 update: These instructions should work for OpenBSD 6.8. Do not assume they work well, or even work for any other version. Here's why: I wrote this post for OpenBSD 6.1. The instructions worked up to 6.4. Then there was a very slight syntax change for httpd.conf. While I caught this for my own httpd.conf file, I forgot to update this blog post for almost 18 months. The instructions were broken. It is only thanks to reader L.R. that the post has been fixed and made to work again on OpenBSD 6.7. There's a reason why the OpenBSD devs tell people to use the man pages and official FAQ, not random webpages!

These instructions cover using Let's Encrypt, OpenBSD's httpd, and the acme-client program to serve websites using SSL certificates.

A caveat: these are very basic instructions from somebody who has been using httpd for a while but has just set up SSL for the first time. They may not make sense if you're trying to learn both httpd and how to use it with SSL.

I am indebted to Michael W. Lucas's book Relayd and Httpd Mastery for walking me through the process. Between it, the files in OpenBSD's /etc/examples/ directory, OpenBSD's manual pages, and of course the developers' commitment to simplicity, setting up httpd is painless.

If there are mistakes below, they are solely mine.

1. Getting httpd ready

We will get our SSL certificate from a certificate provider called Let's Encrypt. We do this by running a program called acme-client. One of the things acme-client does is write some files and make them publically available on our site, so as to prove to Let's Encrypt that we are the site's owner.

Configure httpd and your site to work with acme-client. This is really easy! Just configure your /etc/httpd.conf file to look mostly like this:

You just need to add

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

to your /etc/httpd.conf file. As such, mine looks like this:

server "www.increasinglyadequate.com" {
    alias "increasinglyadequate.com"
    listen on egress port 80
    log style combined
    root "/htdocs/increasinglyadequate"
    # The next five lines are what you need for acme-client:
    location "/.well-known/acme-challenge/*" {
        root "/acme"
        request strip 2
        directory no auto index
    }
}

Now run rcctl restart httpd so your changes take effect.

2. Getting your certificate via acme-client

acme-client is responsible for getting and renewing your certificates. The file /etc/acme-client.conf tells acme-client how to operate. Configuring this file is also very simple!

The file comes pre-loaded with the appropriate settings for Let's Encrypt. Leave these be. At the bottom of the file you will find a commented-out section containing an example configure for a domain. All you'll need to do is change the name "example.com" to whatever your domain name is. That's it.

By default, acme-client stores its files in a couple of directories. I am not a fan of this because I am slow and like everything in one place. Michael W. Lucas is smart and even he advocates a naming scheme other than the default. His scheme uses subdirectories to indicate that the files are managed through acme-client and that they pertain to a particular domain (e.g., he recommends using a directory like /etc/ssl/acme/increasinglyadequate.com/). Since I have just a couple of sites and am using just one SSL certificate provider, I'll stuff everything into /etc/ssl/. You do whatever you want.

Anyhow, my domain section for www.increasinglyadequate.com looks like this:

domain www.increasinglyadequate.com {
    alternative names { increasinglyadequate.com }
    domain key "/etc/ssl/private/www.increasinglyadequate.com.key"
    domain full chain certificate "/etc/ssl/www.increasinglyadequate.com.fullchain.pem"
    sign with letsencrypt
}

Note that you can have multiple domains listed in this file.

Assuming you've followed step 1 and already configured /etc/httpd.conf/ with the appropriate lines, you are ready to get your certificate.

# acme-client -v www.increasinglyadequate.com

Because of the -v flags for verbosity, you'll get a lot of output. But at the end you should see something like this:

...
acme-client: /etc/ssl/increasinglyadequate.fullchain.pem: created

Verify:

# ls -l /etc/ssl/www*

-r--r--r--  1 root  wheel    3859 Jul  8 16:52 www.increasinglyadequate.com.fullchain.pem

# ls -l /etc/ssl/private/

-r--------  1 root  wheel    3272 Jul  8 16:47 www.increasinglyadequate.com.key

If you get a message indicating failure, it's most likely because you haven't configured /etc/httpd.conf as in step 1 or because of some typo in /etc/acme-client.conf.

3. Setting up httpd to serve your site using SSL

This configuration file should only be used if you have completed the previous steps. In particular, if you redirect http traffic to https, but https doesn't work yet, acme-client won't be able to communicate with Let's Encrypt.

At any rate, provided you've got your SSL certificate, we can now tell httpd to serve your site using it, and we can tell httpd to redirect all http traffic to https. Just edit /etc/httpd.conf to look something like this:

# This block redirects port 80 traffic to port 443; all the actual configuration
# options can go underneath the block containing tls details.

server "www.increasinglyadequate.com" {
    alias "increasinglyadequate.com"
    listen on egress port 80
    block return 301 "https://$SERVER_NAME$REQUEST_URI"
}

server "www.increasinglyadequate.com" {
    alias "increasinglyadequate.com"
    listen on egress tls port 443
    hsts
    tls certificate "/etc/ssl/increasinglyadequate.fullchain.pem"
    tls key "/etc/ssl/increasinglyadequate.key"
    log style combined
    root "/htdocs/increasinglyadequate"
    # The next five lines are for acme-client:
    location "/.well-known/acme-challenge/*" {
        root "/acme"
        request strip 2
        directory no auto index
    }
}

Once again, restart httpd with rcctl restart httpd. Additionally, make sure that your firewall and router allow traffic on to reach port 443 on the machine running httpd.

P.S. The hsts line should force browsers to automatically switch from http to https. As such, you may be able to omit the entire section that exists solely for redirecting http traffic, but I read that it may be best to keep both since some clients (including webcrawlers) don't use this HSTS feature.

Make sure your certificates get renewed:

The manual page for acme-client has this simple advice: "A daily cron(8) job can renew the certificates: acme-client example.com && rcctl reload httpd".

That's fine, but since this command is going to be done just once a day, I might as well let OpenBSD's daily system maintenance script take care of it. That way I can read about it with the usual daily system maintenance mail. Plus this saves me from having to fuck around with cron's syntax and from getting a separate email just about this command.

crontab calls /etc/daily at 01:30 every day. One of /etc/daily's commands is to run the script /etc/daily.local if it exists. So I created an /etc/daily.local script that looks a bit like this:

#!/bin/sh

acme-client -v www.increasinglyadequate.com
rcctl reload httpd

Conclusion

Please bear in mind that I have little idea of what I'm doing, that I haven't thoroughly tested these instructions, and that even if they work now, they may not work next week. If you actually want to know how all of this works, get yourself a copy of Lucas's book.