Improved Let’s Encrypt certificate renewal on Bitnami WordPress

I’ve followed the instructions from Bitnami for installing Let’s Encrypt certificate on Amazon Lightsail instance with Bitnami WordPress preloaded. The guide is very user-friendly and easy to follow. However, there is room for improvement in step 5 in certificate renewal.

Bitnami have proposed the following script for certificate renewal. It stops Apache, renew the certificate with lego, and start Apache.

#!/bin/bash

sudo /opt/bitnami/ctlscript.sh stop apache
sudo /usr/local/bin/lego --email="EMAIL-ADDRESS" --domains="DOMAIN" --path="/etc/lego" renew
sudo /opt/bitnami/ctlscript.sh start apache

And the following entry in crontab

0 0 1 * * /etc/lego/renew-certificate.sh 2> /dev/null

There are few issue with this implementation

  • Certificate update will be triggered on 00:00 on the first day of a month. If the server was not running at that time, or the Let’s Encrypt service is busy because a large number of sites have this type of implementation, the certificate renewal may fail.
  • If an renewal was failed, the next renewal will be triggered in the next month.
  • Apache stop before the renewal and start after lego. This may increase unnecessary downtime.

And here is my implementation – it will renew the certificate only if the certificate is going to expire in 7 days. Change EMAIL-ADDRESS and DOMAIN respectively and write to /home/bitnami/cert-update

#!/usr/bin/env ruby
# encoding: utf-8

require "openssl"

LE_EMAIL    = "EMAIL-ADDRESS"
CERT_DOMAIN = "DOMAIN"
CERT_PATH   = "/etc/lego/certificates/#{CERT_DOMAIN}.crt"
RENEW_DAYS_BEFORE_EXPIRE = 7

raw = File.read(CERT_PATH)
certificate = OpenSSL::X509::Certificate.new(raw)
expire_time = certificate.not_after
now = Time.now
seconds_remaining = expire_time - now
days_remaining = seconds_remaining/60/60/24

$stdout.puts "Current time: #{now}"
$stdout.puts "Expiration:   #{expire_time}"
$stdout.puts "Expire in #{days_remaining.round(1)} day(s)"

if days_remaining < RENEW_DAYS_BEFORE_EXPIRE
  $stdout.puts "Starting certificate renew"
  system("/opt/bitnami/ctlscript.sh stop apache")
  system("/usr/local/bin/lego --email='#{LE_EMAIL}' --domains='#{CERT_DOMAIN}' --path='/etc/lego' renew")
  system("/opt/bitnami/ctlscript.sh restart apache")
else
  $stdout.puts "Do nothing"
end

Make the file executable and install Ruby

chmod +x /home/bitnami/cert-update
sudo apt-get install ruby

And run crontab -e then append the following line. It will run the script twice a day, between 00:00~12:00 and 12:00~24:00. The perl script randomises the execution time (I learned this from the certbot) and use flock to prevent concurrent execution.

0 */12 * * * perl -e 'sleep int(rand(43200))' && flock /home/bitnami/cert-update.lock sudo /home/bitnami/cert-update > /home/bitnami/cert-update.log 2>&1

Leave a Reply

Your email address will not be published. Required fields are marked *