Summary
OpenBSD’s acme-client
acme-client is the default Automatic Certificate Management Environment (ACME) client on OpenBSD, installed at the same time when the OS is.
To configure acme-client.conf is easy and enables us to get certificates with Let’s Encrypt or another.
The basic usage is:
# # configure
# nvim /etc/acme-client.conf
# # get or update (for renewal) certificate
# acme-client <domain>
# # revoke certificate
# acme-client -r <domain>
How about CertBot
Besides, we know there is another option. Yes, CertBot by EFF (Electronic Frontier Foundation), a very popular client. Although we can get it via pkg_add certbot, there was sometimes a problem around permissions on OpenBSD when renewing the certificate.
Environment
- OS: OpenBSD 7.0
- ACME client: OpenBSD acme-client
- Certificate Authority: Let’s Encrypt
- Web server: OpenBSD httpd
How to use
Configure acme-client.conf
Well, let’s see the conf file which is officially provided by OpenBSD.
$ cat /etc/examples/acme-client.conf
There are clear examples:
#
# $OpenBSD: acme-client.conf,v 1.4 2020/09/17 09:13:06 florian Exp $
#
authority letsencrypt {
api url "https://acme-v02.api.letsencrypt.org/directory"
account key "/etc/acme/letsencrypt-privkey.pem"
}
authority letsencrypt-staging {
api url "https://acme-staging-v02.api.letsencrypt.org/directory"
account key "/etc/acme/letsencrypt-staging-privkey.pem"
}
authority buypass {
api url "https://api.buypass.com/acme/directory"
account key "/etc/acme/buypass-privkey.pem"
contact "mailto:[email protected]"
}
authority buypass-test {
api url "https://api.test4.buypass.no/acme/directory"
account key "/etc/acme/buypass-test-privkey.pem"
contact "mailto:[email protected]"
}
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
}
Let’s copy it:
$ # well, you can see there is none by default
$ ls /etc/acme-client*
$ doas cp -p /etc/examples/acme-client.conf /etc/
Then edit:
$ doas nvim /etc/acme-client.conf
like below:
- 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
- }
+ domain your.domain {
+ #alternative names { secure.example.com }
+ domain key "/etc/ssl/private/your.domain.key"
+ domain full chain certificate "/etc/ssl/your.domain.fullchain.pem"
+ sign with letsencrypt
+ }
OK. your.domain is now ready for “sign with letsencrypt”.
Start OpenBSD httpd as web server
Be sure to httpd listens on egress:
server "your.domain" {
listen on egress port 80
# ...
}
Well, you may also find the example of httpd.conf in /etc/examples/.
Of course, it has to be started:
$ # case disabled:
$ doas rcctl start -f httpd
$ # case enabled:
$ doas rcctl restart httpd
Request certificate
Just run the single command below, and you will get the sweet certificate:
$ doas acme-client -v your.domain
Well, -v is for verbose and optional.
There are other methods. How to update it:
$ # the same command above
$ doas acme-client your.domain
Moreover, how to revoke it:
$ doas acme-client -r your.domain
Notes
Periodical update is required
Remember Let’s Encrypt certificate lasts only 90 days. You have to update it periodically.
One way to do so is to use cron.
Let’s Encrypt has their Rate Limits
Be careful about that Let’s Encrypt has several rate limits.
For example,
- “Certificates per Registered Domain”. It is 50 per week.
Also,
- “Duplicate Certificate”. You can renew the same certificate up to 4 times (totally 5 times) per week.
Here is their documentation on it.
Side story: The history and the changelog
acme-client first appeared in 6.1.
In 6.5 or below (to 6.1), you had to use more options to get certificate:
$ doas acme-client -ADv your.domain
In 6.6, acme-client became to able to communicate with the v02 Let’s Encrypt API. Therefore, -A and -D have not been necessary any more. Nice improvement.
Troubleshooting: When the challenge failed
You might sometimes see such errors and, sadly, fail:
acme-client: /etc/ssl/private/your.domain: generated RSA domain key
acme-client: https://acme-v02.api.letsencrypt.org/directory: directories
acme-client: acme-v02.api.letsencrypt.org: DNS: xxx.xxx.xxx.xxx
acme-client: dochngreq: https://acme-v02.api.letsencrypt.org/acme/authz-v3/84303399960
acme-client: challenge, token: (...), uri: https://acme-v02.api.letsencrypt.org/acme/chall-v3/84303399960/MQGXJQ, status: 0
acme-client: /var/www/acme/(...): created
acme-client: https://acme-v02.api.letsencrypt.org/acme/chall-v3/84303399960/MQGXJQ: challenge
acme-client: order.status 0
acme-client: dochngreq: https://acme-v02.api.letsencrypt.org/acme/authz-v3/84303399960
acme-client: challenge, token: (...), uri: https://acme-v02.api.letsencrypt.org/acme/chall-v3/84303399960/MQGXJQ, status: -1
acme-client: order.status -1
acme-client: dochngreq: https://acme-v02.api.letsencrypt.org/acme/authz-v3/84303399960
acme-client: Invalid response from http://your.domain/.well-known/acme-challenge/(...) [2606:4700:3031::6815:3ae6]: \\"\\u003c!DOCTYPE html\\u003e\\\\n\\u003chtml\\u003e\\\\n\\u003chead\\u003e\\\\n\\u003cmeta charset=\\\\\\"utf-8\\\\\\"\\u003e\\\\n\\u003ctitle\\u003e404 Not Found\\u003c/title\\u003e\\\\n\\u003cstyle type=\\\\\\"text/css\\\\\\"\\u003e\\u003c!--\\\\nbody { background-\\"
acme-client: bad exit: netproc(87417): 1
It is 404 aka page-is-missing error. In other cases, it may 403 aka access-is-denied.
They are because your web server returned “bad response”. Let’s mitigate it by properly mapping response challenges:
server "your.domain" {
listen on egress port 80
+ # acme-client
+ location "/.well-known/acme-challenge/*" {
+ root "/acme"
+ request strip 2
+ }
# ...
}
Then restart httpd:
$ # case disabled:
$ doas rcctl -f restart httpd
$ # case enabled:
$ doas rcctl restart httpd
It must be fixed.
# acme-client -v your.domain
I hope you meet success like this:
acme-client: https://acme-v02.api.letsencrypt.org/directory: directories
acme-client: acme-v02.api.letsencrypt.org: DNS: xxx.xxx.xxx.xxx
acme-client: dochngreq: https://acme-v02.api.letsencrypt.org/acme/authz-v3/84304202350
acme-client: challenge, token: (...), uri: https://acme-v02.api.letsencrypt.org/acme/chall-v3/84304202350/NVmlRA, status: 0
acme-client: /var/www/acme/(...): created
acme-client: https://acme-v02.api.letsencrypt.org/acme/chall-v3/84304202350/NVmlRA: challenge
acme-client: order.status 0
acme-client: dochngreq: https://acme-v02.api.letsencrypt.org/acme/authz-v3/84304202350
acme-client: challenge, token: (...), uri: https://acme-v02.api.letsencrypt.org/acme/chall-v3/84304202350/NVmlRA, status: 2
acme-client: order.status 1
acme-client: https://acme-v02.api.letsencrypt.org/acme/finalize/58118741/68735636600: certificate
acme-client: order.status 3
acme-client: https://acme-v02.api.letsencrypt.org/acme/cert/(...): certificate
acme-client: /etc/ssl/your.domain: created
Conclusion
You have got the Let’s Encrypt certificate! All what to do is add it to httpd.conf.
server "your.domain" {
listen on * port 80
# acme-client
location "/.well-known/acme-challenge/*" {
root "/acme"
request strip 2
}
+ block return 301 "https://$SERVER_NAME$REQUEST_URI"
}
+ server "your.domain" {
+ listen on * tls port 443
+ tls {
+ certificate "/etc/ssl/your.domain.fullchain.pem"
+ key "/etc/ssl/private/your.domain.key"
+ }
+ # acme-client
+ location "/.well-known/acme-challenge/*" {
+ root "/acme"
+ request strip 2
+ }
+ # ...
+ }
Here, I replaced egress with * to apply also to lo. It is optional.
Your TLS connection based on encryption aka HTTPS is completed.
Happy connecting securely🕊