Automating acme-client Renewal

acme-client's man page provides a nice simple crontab entry for common usage.

The following is for a mail server running dovecot and NO httpd. Though it's somewhat specific, it should be trivial to alter the script for one's own needs.

Shell script

As the use-case is not entirely trivial, we opted for a shell script. The output as it stands is fairly verbose, but again, this would be easy to tweak. This script is perhaps a little over-engineered and is open to improvement. It attempts to only do the necessary work and exit cleanly upon any error.

Create and edit /usr/local/bin/renew_cert (or a filename of your choice - just remember to edit any later commands/files as necessary). The only essential change is to set <your.domain> appropriately.

#!/bin/ksh

# Renew LetsEncrypt certificate(s) for the mail server domain(s)
# Returns
#   0: Success
#   1: Certificate up to date
#   2: Error

# Print message to stdout or stderr
# Messages for stdout are marked [INFO], stderr are marked [ERROR]
# Expects
#   $1: 1|2 (stdout|stderr)
#   $2: Message text
# Silently fails if called badly
print_msg() {
        if [ $# -ne 2 ]; then
                return
        fi
        if [ $1 == "stdout" ]; then
                outfd=1
                tag="[INFO]"
        elif [ $1 == "stderr" ]; then
                outfd=2
                tag="[ERROR]"
        else
                return
        fi
        msg=$2
        echo "${tag} ${msg}" >&$outfd
}

# We need http server for acme-client to communicate with LetsEncrypt
rcctl check httpd
httpd_check=$?

# Only start httpd if it's not already running
if [ $httpd_check -eq 1 ]; then
        print_msg stdout "Starting httpd"
        rcctl -f start httpd
        if [ $? -ne 0 ]; then
                print_msg stderr "Failed to start httpd"
                exit 2
        fi
fi

# Attempt to renew certificate
print_msg "Renewing mail server certificate"
acme-client <your.domain>
acme_check=$?

# Only stop httpd if it was not already running
if [ $httpd_check -eq 1 ]; then
        print_msg stdout "Stopping httpd"
        rcctl stop httpd
fi

if [ $acme_check -eq 2 ]; then
        print_msg stdout "Certificate up to date"
        exit 1
elif [ $acme_check -eq 1 ]; then
        print_msg stderr "Failed to renew certificate"
        exit 2
fi

print_msg stdout "Successfully renewed certificate"

# Serve the new certificate
print_msg stdout "Reloading dovecot"
rcctl reload dovecot
if [ $? -gt 0 ]; then
        print_msg stderr "Failed to reload dovecot"
        exit 2
fi

Permissions

#chmod 700 /usr/local/bin/renew_cert

cronjob

LetsEncrypt will allow certificate renewal if the expiry is within 30 days. We us a simple cronjob to run our script fortnightly.

Output is piped to logger to be written to the system log.

With your favourite text-editor (vim), create or append to /etc/weekly.local:

test 1 -eq $(($(date +\%g) & 1)) && renew_cert | logger -t "[acme renewal]"

The use of test is because running this once every two weeks should be more than enough. The technique was found here and seems reasonable.

By default logger will likely output to /var/log/messages, but this can be confirmed by checking your /etc/syslog.conf. You may also direct logger to output elsewhere; see -p in logger's man page.