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 OpenHTTPd | Configure HTTPd |
Telnet HTTP | Use Telnet to Troubleshoot HTTP |
OpenSSL HTTP | Use OpenSSL to Troubleshoot HTTPS |