Configuring Acme-client

To provide TLS encryption, you will need to provide a TLS certificate for your server. Although it is possible to self-sign your certificates, many end-user clients will reject these certificates as being invalid. For this reason, it's recommended that you get a certificate signed by a trusted certificate authority (CA). In this guide, we'll use OpenBSD's acme-client(1) with Let's Encrypt.

Before You Begin

This guide assumes you have already properly configured and started openhttpd. You will also need properly functioning DNS records for your hostname.

To test if your web server is serving documents properly, use telnet or netcat. To test DNS records, use host or dig.

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

Configuration

First, copy the acme-client.conf(5) template:

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

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

Authority blocks

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

This block 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 certificate signing requests you can make. If you encounter an error and are unable to request a TLS 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 a few days. To avoid this delay, use the authority letsencrypt-staging first and make sure you succeed with that before using the authority letsencrypt.

Although we are using Let's Encrypt for this tutorial, it is important to realize that having the majority of all Internet servers depend upon a single provider is dangerous. For this reason, it would be beneficial for our network to someday run its own Certificate Authority. This can prevent censorship of domains and other security issues.

authority letsencrypt-staging {
        api url "https://acme-staging-v02.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.

For both of these blocks, we will want to add our contact email, so we add contact "me@example.com" inside both blocks. (Make sure to include mailto:):

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(5) 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. You can use buypass to improve CA diversity, or if you have issues with letsencrypt, such as rate-limits. Make sure to replace the contact email with your own email.

Domain Block

Next, we define our domains which we will issue certificate signing requests for:

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

First, replace every appearance of example.com with your own domain.

Each TLS cert is valid for only for a single common name and a set of alternative names that are provided on the certificate. In the default example, the TLS certificate has the common name example.com and the alternative name secure.example.com. You could change the alternative name to www.example.com and mail.example.com. In this guide, we will comment out this line, since we do not want to complicate the example with Subject Alternative Names.

Warning: While a handful of alternative names are fine, using too many alternative names can cause acme-client(1) to fail. We recommend keeping the number of alternative names to 5 or fewer.

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

alternative names { }

If you don't need any alternative names, 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.

Next, the domain key and domain full chain certificate tell acme-client(1) where to put the private key and certificate:

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

Note: By default, acme-client.conf(5) uses the path /etc/ssl/example.com.fullchain.pem for the full chain certificate. Our guide, however, changes the path to /etc/ssl/example.com.crt. We make this change because we later plan to use relayd to provide SSL acceleration, and relayd hard codes the paths it searches for the public and private keypair. relayd(8) will only search for public certificates that end in the .crt suffix; it will ignore certificates that end with the suffix .fullchain.pem.

In our configuration, the public key will go inside the folder /etc/ssl, and the private key will go inside /etc/ssl/private.

The line sign with letsencrypt line tells acme-client(1) which Certificate Authority (which you defined in the authority blocks) to use.

For testing purposes, you may want to change it to letsencrypt-staging. You can also consider using buypass or buypass-test.

Note: staging or testing certificates are not recognized by most browsers and will be rejected as an invalid certificate. After you finish testing with a staging certificate, remember to change this line back to an accepted authority (such as sign with letsencrypt)!

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/ssl/private/example.com.key: generated RSA domain key
acme-client: /etc/acme/letsencrypt-privkey.pem: generated RSA account 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: acme-v02.api.letsencrypt.org: DNS: 2606:4700:60:0:f53d:5624:85c7:3a2c
acme-client: dochngreq: https://acme-v02.api.letsencrypt.org/acme/authz-v3/429811085347
acme-client: challenge, token: ORORKoTwrtvDrb3tfLusX4rbar1BlJALiVx5i-CtZXk, uri: https://acme-v02.api.letsencrypt.org/acme/chall-v3/429811085347/Kca9eQ, status: 0
acme-client: /var/www/acme/ORORKoTwrtvDrb3tfLusX4rbar1BlJALiVx5i-CtZXk: created
acme-client: https://acme-v02.api.letsencrypt.org/acme/chall-v3/429811085347/Kca9eQ: challenge
acme-client: order.status 0
acme-client: dochngreq: https://acme-v02.api.letsencrypt.org/acme/authz-v3/429811085347
acme-client: challenge, token: ORORKoTwrtvDrb3tfLusX4rbar1BlJALiVx5i-CtZXk, uri: https://acme-v02.api.letsencrypt.org/acme/chall-v3/429811085347/Kca9eQ, status: 0
acme-client: /var/www/acme/ORORKoTwrtvDrb3tfLusX4rbar1BlJALiVx5i-CtZXk: created
acme-client: https://acme-v02.api.letsencrypt.org/acme/chall-v3/429811085347/Kca9eQ: challenge
acme-client: order.status 0
acme-client: dochngreq: https://acme-v02.api.letsencrypt.org/acme/authz-v3/429811085347
acme-client: challenge, token: ORORKoTwrtvDrb3tfLusX4rbar1BlJALiVx5i-CtZXk, uri: https://acme-v02.api.letsencrypt.org/acme/chall-v3/429811085347/Kca9eQ, status: 2
acme-client: order.status 1
acme-client: https://acme-v02.api.letsencrypt.org/acme/finalize/2055551047/322888040587: certificate
acme-client: order.status 3
acme-client: https://acme-v02.api.letsencrypt.org/acme/cert/04189299823525c8271ab890b189e381835a: certificate
acme-client: /etc/ssl/example.com.crt: created

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

You now have two certificates, the public key inside /etc/ssl/example.com.crt, and the private key inside /etc/ssl/private/example.com.key:

$ doas ls -l /etc/ssl/example.com.crt /etc/ssl/private/example.com.key
-r--r--r--  1 root  wheel  4797 Feb 25 02:11 /etc/ssl/example.com.crt
-r--------  1 root  wheel  3272 Feb 25 02:10 /etc/ssl/private/example.com.key

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 before expiration, or TLS will stop validating properly. To avoid forgetting, we can automate the request process using crontab.

$ doas crontab -e

Add this line at the bottom:

~       ~       *       *       *       acme-client example.com >> /var/log/acme-client.log 2>&1

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.

Troubleshooting

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

Domain Not Listed

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

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

Here, new.example.com was a new alternative name you added. The solution is to move your old public cert and private key to a new location. This creates a backup in case the next certificate signing request fails. Then, request a new cert again.

$ doas mv /etc/ssl/example.com.crt /etc/ssl/example.com.crt.bak
$ doas mv /etc/ssl/private/example.com.key /etc/ssl/private/example.com.key.bak
$ doas acme-client -Fv example.com

Missing Domain Records

If you see an error message like the following, it's possible that your domain records are missing:

acme-client: DNS problem: NXDOMAIN looking up A for example.com - check that a DNS record exists for this domain; DNS problem: NXDOMAIN looking up AAAA for example.com - check that a DNS record exists for this domain
acme-client: bad exit: netproc(58463): 1

As mentioned earlier, it is important to test that your DNS records are set up properly by using host or dig before running acme-client(1):

$ host example.com
Host example.com not found: 3(NXDOMAIN)

An NXDOMAIN (Non-eXistent DOMAIN) response indicates the DNS record is missing.

Properly configured records should appear as follows:

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

You will either need to speak with your DNS provider or troubleshoot your authoritative nameserver, which on OpenBSD is usually nsd. If DNS records are missing or incorrect, fix these before continuing with acme-client(1).

The IPv4 and IPv6 address must exactly match the IPs that OpenHTTPd is listening on. If they do not match, you must fix this.

Note: You cannot request certificates for a domain that doesn't point to your server! The domain must point to an IP address your server controls.

Lastly, if your DNS record includes an IPv6 address, make sure your web server is listening on IPv6. If the DNS record contains an IPv4 address, make sure your web server is listening on IPv4.

OpenHTTPd Misconfigured

acme-client(1) 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 serving documents properly, use telnet or netcat. Make sure to run telnet and netcat on another computer that is not the web server:

$ telnet example.com 80

Then type these two lines:

GET /index.html HTTP/1.1
Host: example.com

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

Note: A web browser can also be used for testing, but check to make sure that your web browser is not automatically enforcing SSL/TLS. The certificate authority will only check port 80 (plaintext), not port 443 (encrypted with TLS).

Incorrect File Permissions

Double check /var/www and /var/www/acme to ensure they have correct file permissions:

$ 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

See Also:

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