Configuring Acme-client

To enable TLS, you will want a certificate signed by a trusted certificate authority (CA). In this guide, we'll use OpenBSD's acme-client with Let's Encrypt.

Setting up OpenHTTPd

Before you begin, you will need to properly configure and start openhttpd. You will also need a properly functioning DNS records for your hostname, which might look like username.fruit.ircnow.org.

Note: You must have a server block in /etc/httpd.conf listening on port 80. Do not delete this block or else acme-client will not work.

Configuration

First, copy the acme-client.conf template:

$ doas cp /etc/examples/acme-client.conf /etc/acme-client.conf

We'll open up /etc/acme-client.conf and analyze the meaning of each block:

authority letsencrypt {
        api url "https://acme-v02.api.letsencrypt.org/directory"
        account key "/etc/acme/letsencrypt-privkey.pem"
}

This defines the Certificate Authority letsencrypt. It provides the API URL and the location of the account key.

Note: Let's Encrypt rate-limits the number of SSL certs you can request. If you encounter an error and are unable to request an SSL cert, please fix all errors before requesting again. If you request too many certs in a short time, your domain will get blacklisted for a few hours or days.

Although we are using Let's Encrypt for this tutorial, it is important to note that Let's Encrypt currently has a monopoly on free SSL certs. For this reason, IRCNow wants to run its own Certificate Authority in case Let's Encrypt should try to censor our domains.

authority letsencrypt-staging {
        api url "https://acme-staging.api.letsencrypt.org/directory"
        account key "/etc/acme/letsencrypt-staging-privkey.pem"
}

letsencrypt-staging is a staging server which you can use to practice requesting fake certificates. The rate limits for the staging server are less strict, so you should practice first with this CA.

To both of these blocks, we will want to add our contact email, so we add contact "me@example.com" inside both blocks:

authority letsencrypt {
        api url "https://acme-v02.api.letsencrypt.org/directory"
        account key "/etc/acme/letsencrypt-privkey.pem"
        contact "mailto:me@example.com"
}

authority letsencrypt-staging {
        api url "https://acme-staging-v02.api.letsencrypt.org/directory"
        account key "/etc/acme/letsencrypt-staging-privkey.pem"
        contact "mailto:me@example.com"
}

Next, the default acme-client.conf defines two more authorities:

authority buypass {
        api url "https://api.buypass.com/acme/directory"
        account key "/etc/acme/buypass-privkey.pem"
        contact "mailto:me@example.com"
}

authority buypass-test {
        api url "https://api.test4.buypass.no/acme/directory"
        account key "/etc/acme/buypass-test-privkey.pem"
        contact "mailto:me@example.com"
}

These two blocks are the same as for letsencrypt, but with the alternative provider buypass. Make sure to replace the contact email with your own email.

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

This configures acme-client for the domain example.com. You'll want to replace every appearance of example.com with your own domain, which might look like username.fruit.ircnow.org.

Each SSL cert is valid only for a common name and a set of alternative names that are provided on the certificate. For example, an SSL certificate might have the common name example.ircnow.org and the alternative names fruit.ircnow.org and vegetable.ircnow.org.

If you use too many alternative names, an acme-client certificate request has a higher chance of failure. So, I recommend keeping the number of alternative names to under 5.

Warning: Having the alternative names directive with nothing inside will cause errors. The below will cause errors:

alternative names { }

If you don't need any alternative names, you should comment this line out by putting a # at the beginning of the line, like so:

#        alternative names { }

Note: If you add an alternative name to the conf file, but the cert already exists, you must remove the old public cert first before requesting a new one. Otherwise, you will get unknown SAN error -- acme-client will complain there is an unknown Subject Alternative Name.

The domain key and domain full chain certificate tell acme-client where to put the private key and certificate:

        domain key "/etc/ssl/private/example.com.key"
        domain full chain certificate "/etc/ssl/example.com.fullchain.pem"

You will want to replace example.com with your real domain. The public key should go inside /etc/ssl and the private key should go inside /etc/ssl/private.

If you want to sign with buypass, test a staging certificate (to avoid using up your rate-limit), or switch to another authority, then edit this line:

        sign with letsencrypt

Change it to match one of your defined authorities. For example:

  1. To test with letsencrypt-staging, replace it with sign with letsencrypt-staging.
  2. To sign with buypass, replace it with sign with buypass.

Note: staging certificates are not recognized by most browsers and will be rejected as an invalid certificate. After you finish testing with a staging certificate, you will want to get a properly signed one.

Requesting Certificates

After you have finished configuring the conf file, we can request certificates:

$ doas acme-client -Fv example.com

If there are no errors, you should see something similar to the following output:

$ doas acme-client -Fv example.com
acme-client: /etc/acme/letsencrypt-privkey.pem: generated RSA account key
acme-client: /etc/ssl/private/example.com.key: generated RSA domain key
acme-client: https://acme-v02.api.letsencrypt.org/directory: directories
acme-client: acme-v02.api.letsencrypt.org: DNS: 172.65.32.248
acme-client: dochngreq: https://acme-v02.api.letsencrypt.org/acme/authz-v3/11133258838
acme-client: challenge, token: uWHZmqhx6NEpcv25LEvodMAeymB1guTFVtyktVzkJgs, uri: https://acme-v02.api.letsencrypt.org/acme/chall-v3/11133258838/_UI3-A, status: 0
acme-client: /var/www/acme/uWHZmqhx6NEpcv25LEvodMAeymB1guTFVtyktVzkJgs: created
acme-client: https://acme-v02.api.letsencrypt.org/acme/chall-v3/11133258838/_UI3-A: challenge
acme-client: order.status 0
acme-client: dochngreq: https://acme-v02.api.letsencrypt.org/acme/authz-v3/11133258838
acme-client: challenge, token: uWHZmqhx6NEpcv25LEvodMAeymB1guTFVtyktVzkJgs, uri: https://acme-v02.api.letsencrypt.org/acme/chall-v3/11133258838/_UI3-A, status: 2
acme-client: order.status 1
acme-client: https://acme-v02.api.letsencrypt.org/acme/finalize/113861127/8112730231: certificate
acme-client: order.status 3
acme-client: https://acme-v02.api.letsencrypt.org/acme/cert/03f7fd846802cb0689c2bbd7b6f5e89eb66b: certificate
acme-client: /etc/ssl/example.com.fullchain.pem: created

Pay attention to the last line: it says that the public certificate was generated. If you see that, it's a success!

You now have two certificates, the public key inside /etc/ssl/example.com.fullchain.pem, and the private key inside /etc/ssl/private/example.com.key (or wherever you changed the path to):

$ doas ls -l /etc/ssl/example.com.fullchain.pem /etc/ssl/private/example.com.key
-r--r--r--  1 root  wheel  4797 Feb 25 02:11 /etc/ssl/jrmu.coconut.ircnow.org.fullchain.pem
-r--------  1 root  wheel  3272 Feb 25 02:10 /etc/ssl/private/jrmu.coconut.ircnow.org.key

Troubleshooting

If acme-client fails, there are several possible causes:

Missing Domain Records

It's possible that your domain records are missing. Run this command, replacing example.com with your real hostname:

$ host example.com

You should see one or two records like the following:

example.com has address 93.184.216.34
example.com has IPv6 address 2606:2800:220:1:248:1893:25c8:1946

The IPv4 and IPv6 address must exactly match the IPs that OpenHTTPd is listening on.

Note: You cannot request a domain you don't own! The domain must point to an IP you own.

 There are a few possible mistakes:
  1. Your web server is listening only one IPv4 but your DNS record includes IPv6; or vice versa.
  2. You have the wrong IP addresses.
  3. DNS records are missing.

If you have missing records, you will see this response:

Host example.com not found: 3(NXDOMAIN)

You will either need to speak with your DNS provider or you will need to troubleshoot nsd?.

OpenHTTPd Misconfigured

acme-client uses the "http-01" challenge. A file is created with a special message in /var/www/acme/, and the certificate authority requests that file using the URL http://example.com/.well-known/acme-challenge/*. If openhttpd is not configured and running properly, acme-client won't work.

To test if your web server is running properly, use telnet (replacing example.com with your domain):

$ telnet example.com 80
GET /index.html HTTP/1.1
Host: example.com

You should a response similar to the one below:

HTTP/1.0 302 Found
Date: Tue, 23 Feb 2021 14:01:28 GMT
OpenBSD httpd
Connection: close
Content-Type: text/html
Content-Length: 486
Location: https://example.com/index.html
...

If you do not get this response, double check your openhttpd configuration.

Note: Using the telnet command above is more reliable than visiting the URL in a web browser. By default, httpd.conf (and most web browsers) will forward all requests for port 80 to port 443. As a result, your web browser will see what is listening on port 443, but the certificate authority will test port 80 only.

Domain Not Listed

If you add a new alternative name inside your domain block in /etc/acme-client.conf, you will see this error:

acme-client: /etc/ssl/example.com.fullchain.pem: domain not listed: new.example.com

Here, new.example.com was a new alternative name I added. The solution is to move your old public cert and private key to a new location, then request the cert again:

$ doas mv /etc/ssl/example.com.fullchain.pem /etc/ssl/example.com.fullchain.pem.bak
$ doas mv /etc/ssl/private/example.com.key /etc/ssl/private/example.com.key.bak

Again, you must replace example.com with your actual domain. Then:

$ doas acme-client -Fv example.com

Incorrect File Permissions

Double check the file permissions for /var/www and /var/www/acme:

$ ls -ld /var/www /var/www/acme
drwxr-xr-x  10 root  daemon  512 Oct  5 07:47 /var/www
drwxr-xr-x   2 root  daemon  512 Oct  5 07:47 /var/www/acme

Automation

Let's Encrypt TLS certs expire after 90 days, while Buypass certs expire after 180. For both, you must remember to request the TLS cert or TLS will stop working. To avoid forgetting, we can automate the request process using crontab?.

$ doas crontab -e

Add this line at the bottom:

~       *       *       *       *       acme-client example.com && rcctl reload httpd

This cronjob will check the certificate once each day, at a random time of day, to see if it needs to be renewed. If it does, it will renew the cert, then reload openhttpd to use it.

See Also:

Configure OpenHTTPdConfigure HTTPd
Telnet HTTPUse Telnet to Troubleshoot HTTP
OpenSSL HTTPUse OpenSSL to Troubleshoot HTTPS