Introduction Link to heading

This is just my guide on obtaining a TLS certificate via acme.sh on a FreeBSD system. The write up is using linode to let us perform a DNS challenge (a DNS is required if you want a wildcard certificate) and using nginx as the webserver. The guide assumes that you have set up DNS records, obtained API keys (if required), and have set up your web server of choice. We won’t go into how to do any of these things because the steps will differ depending on your set up. This guide will only focus on installing acme.sh onto FreeBSD, obtaining a certificate, setting up automatic renewal, and letting acme reload the nginx webserver whenever the certificate has been renewed.

Software Link to heading

FreeBSD 14.0-RELEASE-p6 using the latest packages:

  • acme.sh 3.0.7_1
  • sudo 1.9.15p5_4

Installing acme.sh Link to heading

Obtaining a certificate as the acme user Link to heading

Running acme.sh as the root user will lead to some strange errors. For example, acme.sh was not able to find the required files to let me do a DNS challenge:

[root@jail ~]# acme.sh --issue --test --dns dns_linode_v4 -d brendans-bits.com -d *.brendans-bits.com --dnssleep 900

<snip>
[Fri Jul  5 21:54:26 AEST 2024] Can not find dns api hook for: dns_linode_v4
<snip>

The solution was to switch to the installed acme user:

[root@jail ~]# su - acme

$ whoami
acme
$ pwd
/var/db/acme

Running these commands as the acme user was successful.

$ acme.sh  --set-default-ca  --server letsencrypt
[Sat Jul  6 16:15:04 AEST 2024] Changed default CA to: https://acme-v02.api.letsencrypt.org/directory

$ acme.sh --register-account --server letsencrypt -m <your@email.com>>
[Sat Jul  6 16:16:02 AEST 2024] Create account key ok.
[Sat Jul  6 16:16:02 AEST 2024] Registering account: https://acme-v02.api.letsencrypt.org/directory
[Sat Jul  6 16:16:03 AEST 2024] Registered
<snip>

$ acme.sh --issue --dns dns_linode_v4 -d brendans-bits.com -d *.brendans-bits.com --dnssleep 900

<snip>
[Sat Jul  6 16:41:53 AEST 2024] Cert success.
<snip>
[Sat Jul  6 16:41:53 AEST 2024] Your cert is in: /var/db/acme/certs/brendans-bits.com_ecc/brendans-bits.com.cer
[Sat Jul  6 16:41:53 AEST 2024] Your cert key is in: /var/db/acme/certs/brendans-bits.com_ecc/brendans-bits.com.key
[Sat Jul  6 16:41:53 AEST 2024] The intermediate CA cert is in: /var/db/acme/certs/brendans-bits.com_ecc/ca.cer
[Sat Jul  6 16:41:53 AEST 2024] And the full chain certs is there: /var/db/acme/certs/brendans-bits.com_ecc/fullchain.cer

Running acme.sh --list shows that we have successfully retrieved a certificate.

$ acme.sh --list
Main_Domain        KeyLength  SAN_Domains          CA               Created               Renew
brendans-bits.com  "ec-256"   *.brendans-bits.com  LetsEncrypt.org  2024-07-06T06:41:53Z  2024-09-03T06:41:53Z

We can also see that acme.sh has also saved these details into the certificate configuration file:

$ cat certs/brendans-bits.com_ecc/brendans-bits.com.conf
Le_Domain='brendans-bits.com'
Le_Alt='*.brendans-bits.com'
Le_Webroot='dns_linode_v4'
<snip>
Le_DNSSleep='900'
<snip>
Le_CertCreateTime='1720248113'
Le_CertCreateTimeStr='2024-07-06T06:41:53Z'
Le_NextRenewTimeStr='2024-09-03T06:41:53Z'
Le_NextRenewTime='1725345713'
<snip>

Install the certificates Link to heading

We need to create a folder to hold the installed certificates:

[root@jail ~]# mkdir -p /usr/local/etc/ssl/brendans-bits.com

[root@jail ~]# chown acme:acme /usr/local/etc/ssl/brendans-bits.com

[root@jail ~]# su - acme

$ acme.sh --install-cert  -d brendans-bits.com --key-file /usr/local/etc/ssl/brendans-bits.com/key.pem --fullchain-file /usr/local/etc/ssl/brendans-bits.com/cert.pem

[Sat Jul  6 16:43:38 AEST 2024] The domain 'brendans-bits.com' seems to have a ECC cert already, lets use ecc cert.
[Sat Jul  6 16:43:38 AEST 2024] Installing key to: /usr/local/etc/ssl/brendans-bits.com/key.pem
[Sat Jul  6 16:43:38 AEST 2024] Installing full chain to: /usr/local/etc/ssl/brendans-bits.com/cert.pem

(I won’t go into detail about how you load the certificates onto your web server, as this process differs for each server.)

Allow acme.sh to reload the webserver Link to heading

We’re going to allow the acme user to use sudo to reload the web server whenever the certificate is renewed.

To do this, we add acme ALL = NOPASSWD: /usr/sbin/service nginx reload to the sudoers file. (Change this command to suit which ever web server you are using)

[root@jail ~]# visudo

# Add the following line into the sudoers file
acme ALL = NOPASSWD: /usr/sbin/service nginx reload

This line allows the acme user to run /usr/sbin/service nginx reload without needing a password.

Then, we switch to the acme user and check that this works:

[root@nextcloud ~]# su - acme
$ whoami
acme
$ sudo service nginx reload
Performing sanity check on nginx configuration:
nginx: the configuration file /usr/local/etc/nginx/nginx.conf syntax is ok
nginx: configuration file /usr/local/etc/nginx/nginx.conf test is successful

If this works, we can re-run the acme.sh --install-cert with reloadcmd "sudo service nginx reload". This will then add the reload command to the certificate configuration file.

$ cat certs/brendans-bits.com_ecc/brendans-bits.com.conf
<snip>
Le_ReloadCmd=''
<snip>

$ acme.sh --install-cert  -d brendans-bits.com --key-file /usr/local/etc/ssl/brendans-bits.com/key.pem --fullchain-file /usr/local/etc/ssl/brendans-bits.com/cert.pem --reloadcmd "sudo service nginx reload"
[Sat Jul  6 16:44:40 AEST 2024] The domain 'brendans-bits.com' seems to have a ECC cert already, lets use ecc cert.
[Sat Jul  6 16:44:40 AEST 2024] Installing key to: /usr/local/etc/ssl/brendans-bits.com/key.pem
[Sat Jul  6 16:44:40 AEST 2024] Installing full chain to: /usr/local/etc/ssl/brendans-bits.com/cert.pem
[Sat Jul  6 16:44:40 AEST 2024] Run reload cmd: sudo service nginx reload
Performing sanity check on nginx configuration:
nginx: the configuration file /usr/local/etc/nginx/nginx.conf syntax is ok
nginx: configuration file /usr/local/etc/nginx/nginx.conf test is successful
[Sat Jul  6 16:44:40 AEST 2024] Reload success

$ cat certs/brendans-bits.com_ecc/brendans-bits.com.conf
<snip>
Le_ReloadCmd='__ACME_BASE64__START_c3VkbyBzZXJ2aWNlIG5naW54IHJlbG9hZA==__ACME_BASE64__END_'
<snip>

You may be wondering what the '__ACME_BASE64_... stuff is all about. The reload command is saved as a base64 encoded string to deal with special characters. Decoding the base64 string reveals the reload command:

$ echo c3VkbyBzZXJ2aWNlIG5naW54IHJlbG9hZA== | base64 --decode
sudo service nginx reload

Install a cronjob to renew the certificate Link to heading

We then let acme.sh create a cronjob to let acme.sh run every day and check whether the certificate needs to be renewed.

$ acme.sh --install-cronjob

$ crontab -l
26 4 * * * /usr/local/sbin/acme.sh --cron --home "/var/db/acme/.acme.sh" > /dev/null

Putting it all together Link to heading

So far, we’ve:

  • successfully obtained a certificate,
  • installed the certificate,
  • allowed acme to reload the webserver, and
  • set up a daily cronjob to check whether the certificate needs to be renewed.

Now it’s time to put it all together and see if all of the steps work:

$ acme.sh --cron --home "/var/db/acme/.acme.sh" --force
[Sat Jul  6 16:54:59 AEST 2024] ===Starting cron===
[Sat Jul  6 16:54:59 AEST 2024] Renew: 'brendans-bits.com'
<snip>
[Sat Jul  6 16:55:06 AEST 2024] Cert success.
<snip>
[Sat Jul  6 16:55:06 AEST 2024] Your cert is in: /var/db/acme/certs/brendans-bits.com_ecc/brendans-bits.com.cer
[Sat Jul  6 16:55:06 AEST 2024] Your cert key is in: /var/db/acme/certs/brendans-bits.com_ecc/brendans-bits.com.key
[Sat Jul  6 16:55:06 AEST 2024] The intermediate CA cert is in: /var/db/acme/certs/brendans-bits.com_ecc/ca.cer
[Sat Jul  6 16:55:06 AEST 2024] And the full chain certs is there: /var/db/acme/certs/brendans-bits.com_ecc/fullchain.cer
[Sat Jul  6 16:55:06 AEST 2024] Installing key to: /usr/local/etc/ssl/brendans-bits.com/key.pem
[Sat Jul  6 16:55:06 AEST 2024] Installing full chain to: /usr/local/etc/ssl/brendans-bits.com/cert.pem
[Sat Jul  6 16:55:06 AEST 2024] Run reload cmd: sudo service nginx reload
Performing sanity check on nginx configuration:
nginx: the configuration file /usr/local/etc/nginx/nginx.conf syntax is ok
nginx: configuration file /usr/local/etc/nginx/nginx.conf test is successful
[Sat Jul  6 16:55:06 AEST 2024] Reload success
[Sat Jul  6 16:55:06 AEST 2024] ===End cron===

And we can check whether the web server is using the new certificates:

(my linux machine)$ openssl s_client -servername nextcloud.brendans-bits.com -connect nextcloud.brendans-bits.com:443 | openssl x509 -inform pem -text | grep 'Timestamp'
depth=2 C = US, O = Internet Security Research Group, CN = ISRG Root X1
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = E5
verify return:1
depth=0 CN = brendans-bits.com
verify return:1
                Signed Certificate Timestamp:
                    Timestamp : Jul  6 06:55:15.811 2024 GMT
                Signed Certificate Timestamp:
                    Timestamp : Jul  6 06:55:15.818 2024 GMT

Since 06:55 GMT matches 16:55 AEST, we can confirm that forcibly running acme.sh --cron:

  • renewed the certificate,
  • installed the certificate, and
  • reloaded the web server.