I’ve finally decided to automate the TLS certificate renewal and deployment now that I have a mail server and a certificate issue might hinder the ability of this server to send and receive mail, especially to/from major providers like GMail or Outlook.

Renewal

So, first thing first, I’ve changed certbot’s renewal method to --webroot and set the webfoot path to a fixed location, in this case /var/www/well-known which inside of it has the following link:

$ ls -la
[...]
lrwxrwxrwx 1 root root  1 Sep 28 14:29 .well-known -> .

As to provide a path for the ACME server’s request to the /.well-known directory. Certbot will use the /var/www/well-known directory for all certificate renewals regardless of domain, so we need to tell the HTTP server to route all requests for /.well-known from all domains to that directory. To make things easier, a configuration include was created at /etc/nginx/well-known.inc containing:

location /.well-known/ {
  root /var/www/well-known;
}

And on each and every vhost configuration file, the include well-known.inc was added, thus all of the vhost /.well-known/ requests go to the same directory.

Deployment

For deployment, whenever a certificate is renewed, any service currently using it needs to be restarted as to discard the now-revoked certificate and load the new. To do that, Certbot provides the renewal-hooks directory under the its configuration directory, in my case /etc/letsencrypt, which contains the deploy post and pre directories. scripts under pre and post get called previous and post certificate renewal unconditionally, even if certificate renewal fails, meanwhile, the scripts under deploy will be called on each and every successful certificate renewal. So a deploy script will need to restart the services that have been affected by each certificate renewal and, to know which certificate has been affected, the $RENEWED_LINEAGE variable contains the path to the certificate directory which after passing it to basename we get the domain that has been just successfully renewed.

So to just restart the affected services, our deploy script may look like:

#!/bin/sh

#Extract domain name from certificate path
DOMAIN=`basename "$RENEWED_LINEAGE"`
echo "Executing deploy hooks for <$DOMAIN>"

if   [ "$DOMAIN" = "heavydeck.net" ]
then
  #Restart web server *and* mail services
  service nginx restart
  service opendkim restart
  service postfix restart
  service dovecot restart
elif [ "$DOMAIN" = "other-web-host.net" ]
then
  #Just restart the web server
  service nginx restart
else
        echo "...No deploy hooks for <$DOMAIN>"
fi

Automation

Finally, to wrap it all up, the process needs to be automated and, the easiest way is just adding a crontab entry to attempt certificate renewal daily. The entry looks like this:

0 0 * * * root certbot -n -q renew 2>/tmp/certbot.err >/tmp/certbot.log || echo -e 'Certbot failed to execute correctly\nCheck logs for more info'

Or just the command in a more easy to visualize way:

certbot -n renew 2>/tmp/certbot.err >/tmp/certbot.log \
    || echo -e 'Certbot failed to execute correctly\nCheck logs for more info'

In which certbot is called non interactively, and its output saved to /tmp/certbot.err and /tmp/certbot.log on each call. If for some reason the command fails, it will echo a warning message, this will make cron send system email to root, warning the system administrator (me) that something bad has happened during renewal. If certificates are not yet due for renewal, or if certificates renew successfully, no alert will be sent.

And there it is, my current unattended solution for certificate renewal and deployment on my mail/web server. I hope someone else finds it useful too ☺️