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: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"
}
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🕊