OpenBSD: HTTPS Web Server With Let's Encrypt

Please note: this version of the article applies to an outdated version of OpenBSD. The current version can be found here.

This article explains how to set up a secure Web server that responds only to HTTPS requests using the default software of the OpenBSD operating system version 6.3, i.e. httpd(8), and acme-client(8). Please mind however, that only to HTTPS is not entirely correct: the Web server has to answer to plain HTTP requests for the sake of renewal of X.509 certificates issued by Let's Encrypt, but all the rest of requests will be served using HTTPS.

This article discusses HTTPS in particular, however certificates from the Let's Encrypt project have ‘TLS Web Server Authentication’ and ‘TLS Web Client Authentication’ as their Extended Key Usage, hence, they can be used for securing any application that communicates over the TLS protocol, i.e. not necessarily a Web server. I can testify that I successfully use such a certificate with an OpenSMTPD server for securing incoming mail of a domain that I manage.

Before You Begin

Your host must meet certain conditions during the enrollment (i.e. the very first time when the acme-client(1) is run for a particular domain) and renewals (i.e subsequent periodic executions of acme-client(1) for the purpose of validation and re-issuing) of certificates.


Every host name that is listed in the alternative names clause of acme-client.conf(5) must resolve to an IP address using the DNS protocol.

HTTP server

Every host name that is listed in the alternative names clause of acme-client.conf(5) must be reachable using the HTTP (at the time of this writing, the acme-client(1) supports only the HTTP Challenge, a.k.a http-01, as defined by the ACME Working Group). To satisfy that, you must configure, enable, and verify the proper operation of the HTTP server— prior to enrollment.

Important: when acme-client(1) requests the enrollment, the Let's Encrypt's server first creates an unauthorised account for the domain being enrolled, then sends it an HTTP Challenge, and then authorizes the account—if the challenge was properly answered by the domain (i.e. by the HTTP server). HTTP server's failure to answer the challenge during the enrollment will bring the domain's account (at the Let's Encrypt's server) into an intermediate state that will prevent subsequent attempts of enrollment. Therefore, do validate that the HTTP server responds prior to launching acme-client(1) for the first time. Use another computer, preferably from another network, to ensure that the DNS and the HTTP server are configured properly and the external traffic to this HTTP server is not blocked by a firewall. Do not proceed unless you are absolutely sure that everything is in order.


For the purpose of explanation, I assume below that you have OpenBSD 6.3 installed on a machine that is known both as and I further assume that you intend to apply minimal changes to the default configuration of the system.

The very basic httpd.conf(5) that supports running the acme-client(1) can be created as follows

cat <<EOF | doas tee /etc/httpd.conf
server "default" {
    listen on egress port 80
    root "/htdocs"
    directory no index
    location "/.well-known/acme-challenge/*" {
        root {"/acme", strip 2}

Now start httpd(8):

doas rcctl -f start httpd
(Here I use the flag -f to force starting httpd(8) without enabling it first, i.e. httpd(8) will not be started on the next boot. To me this is a matter of precaution because I perceive this configuration as temporary.)

Edit acme-client.conf(5) as follows:

doas vi /etc/acme-client.conf
Using the editor, you should make three changes:
  1. Globally replace all appearances of with the name of your domain (in our example,
  2. Completely uncomment the clause domain
  3. In the same clause, locate the sub-clause alternative names and replace its contents completely with the list of host names that you want to have as the value of the attribute X509v3 Subject Alternative Name of the certificate that you age going to apply for with Let's Encrypt.

Your version of acme-client.conf(5) should be similar to the following:

authority letsencrypt {
        api url ""
        account key "/etc/acme/letsencrypt-privkey.pem"

authority letsencrypt-staging {
        api url ""
        account key "/etc/acme/letsencrypt-staging-privkey.pem"

domain {
       alternative names { }
       domain key "/etc/ssl/private/"
       domain certificate "/etc/ssl/"
       domain full chain certificate "/etc/ssl/"
       sign with letsencrypt

Verify the Operation of HTTP

As explained above, you must verify that all hostname that are listed in the alternative names sub-clause of acme-client.conf are reachable through the HTTP.

To this end, create a small HTML file called test.html under /var/www/acme:

hostname | doas tee /var/www/acme/test.html
Having such a file does not pose a security risk and you can remove this file later.

Use another computer, preferably from another network, to verify the operation of HTTP:

for name in; do
    if ! curl -f "http://${name}/.well-known/acme-challenge/test.html"; then
        echo "${name} is not reachable. Aborting."
If execution of this script produces anything else but the contents of /var/www/acme/test.html as many times as there are hostnames specified, please stop, investigate the cause of the error, correct it, and run the script again.

Do not proceed any further until this test succeeds completely, for every host name listed.

If all goes well, you can remove the test file:

doas rm /var/www/acme/test.html


There is no much to it, run the following:

doas acme-client -vAD
Carefully examine the output (there will be a considerable amount of it): the command must succeed without any errors.

Configuration of the HTTPS Server

Overwrite httpd.conf(5) as follows:

cat <<EOF | doas tee /etc/httpd.conf
server "default" {
    listen on egress port 80
    root "/htdocs"
    directory no index
    location "/.well-known/acme-challenge/*" {
        root {"/acme", strip 2}
    location "/*" {
        block return 301 "https://\$HTTP_HOST\$REQUEST_URI"

server "secure" {
    listen on egress tls port 443 
    tls certificate "/etc/ssl/"
    tls key "/etc/ssl/private/"
    root "/htdocs"
    directory index index.html
Please note that location clauses are matched in the order of their appearance and the first clause that matches ‘wins’, i.e. the remaining clauses are not processed. The first location clause of the HTTP server permits ACME HTTP challenges to be processed, while the second redirects all other requests to HTTPS.

Enable httpd(8) and re-start it:

doas rcctl enable httpd
doas rcctl restart httpd

Prepare for Renewals

Append daily invocation of acme-client to daily(8):

cat <<EOF | doas tee -a /etc/daily.local
acme-client && rcctl reload httpd

When In Trouble

Should you decide to start from the scratch (as the result of an error or because you wish to modify the list of alternative names in acme-client.conf(5)), you must remove all the keys that are known to Let's Encrypt (and, for the sake of correctness, local secret keys as well):

doas rm -f /etc/acme/letsencrypt-privkey.pem \
    /etc/ssl/ \
    /etc/ssl/ \

Vadim Penzin, March 10th, 2018

I hereby place this article into the public domain.
I publish this information in the hope that it will be useful, but without ANY WARRANTY.
You are responsible for any and all consequences that may arise as the result of using this information.