Automating Let's Encrypt certificate renewal and deployment
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 ☺️