I’ve been using Let’s Encrypt SSL certificates for well over 2 years now. Of course, Let’s Encrypt SSL certificates are short-lived so need to be renewed every 3 months or actually well before the 3-month expiry arrives. This is why we check daily for any certificates needing renewal and automatically renew them.
For obtaining and renewing certificates I have always used the
Sadly, some 2 weeks ago renewals failed one evening across 6 different servers all at the same time. This was caused by
Luckily I monitor my renewals daily and always get notified by email so seeing this across 6 servers was troubling indeed. I personally do not like
I don’t like fixing things and I simply do not have things that break like this on any of my servers so for me it’s often
So I set off to write a much more solid, portable and
This still uses
My script creates a solid, self contained and stable Python environment within Conda which is
This bash script below can be run on pretty much any distribution on Linux, it should work just fine.
The script takes care of installing Conda, creating the Conda Python Environment, installing
This simple script is Copyright to me but you are free to use it. If you are going to use it commercially consider being kind enough to buy me some Ko-Fi.
Requirements:
- any command line mailer (for sending emails) – script automatically finds a mailer binary.
Simply save the script (below) in your home folder as
Make the script executable
sudo chmod +x certbot-renewals.sh
Make sure your user has the ability to run
An example of the visudo entry on its own line.
myusername ALL=(ALL) NOPASSWD: /home/myusername/certbot-renewals.sh
Then add an entry to cron. This example below will run every night at 8pm
0 20 * * * sudo /home/myusername/certbot-renewals.sh
Test running the script manually once
sudo ./certbot-renewals.sh
Here it is finally, the script. Enjoy 😉 Make sure to change the email address, email subject,

#!/bin/bash # ----------------------------------------------------------- # Mini(Conda) Environment for Failproof Certbot Renewals # Created by Mitchell Krog: https://github.com/mitchellkrogza # Copyright Mitchell Krog: https://github.com/mitchellkrogza # GIST: https://gist.github.com/mitchellkrogza/547a850a34d022009e5d80e896684eac # Last Updated: 2019-10-01 11:11:00 SAST # ----------------------------------------------------------- # Save as certbot-renewals.sh # Execute with sudo | sudo ./certbot-renewals.sh # Uninstall any other certbot installed through apt, yum or other method # Run through cron and make sure your user is allowed to run sudo commands from cron (visudo) # Uses any command line mailer on your syste, the script will verify a command line mailer exists. # ------------- # User Settings # ------------- recipient="me@myownemail.com" emailsubject="CERTBOT Renewals - Server 1" pythonversion="3.7.4" environmentname="certbot-conda" renewallogfile="/var/log/certbot-renew.log" renewhook="nginx -s reload" # SELECT DNS PLUGINS TO INSTALL # -------------------------------- # 1 = INSTALL / 0 = DO NOT INSTALL # -------------------------------- cloudflare_dns=1 cloudxns_dns=0 digitalocean_dns=0 dnsimple_dns=0 dnsmadeeasy_dns=0 google_dns=0 linode_dns=0 luadns_dns=0 nsone_dns=0 ovh_dns=0 rfc2136_dns=1 route53_dns=0 # ----------------- # End User Settings # ----------------- # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Do Not Modify Anything Below This Line # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # ------------------------ # Set Terminal Font Colors # ------------------------ bold=$(tput bold) red=$(tput setaf 1) green=$(tput setaf 2) yellow=$(tput setaf 3) blue=$(tput setaf 4) magenta=$(tput setaf 5) cyan=$(tput setaf 6) white=$(tput setaf 7) defaultcolor=$(tput setaf default) # ----------- # DNS Plugins # ----------- cloudflare="certbot-dns-cloudflare" cloudxns="certbot-dns-cloudxns" digitalocean="certbot-dns-digitalocean" dnsimple="certbot-dns-dnsimple" dnsmadeeasy="certbot-dns-dnsmadeeasy" google="certbot-dns-google" linode="certbot-dns-linode" luadns="certbot-dns-luadns" nsone="certbot-dns-nsone" ovh="certbot-dns-ovh" rfc2136="certbot-dns-rfc2136" route53="certbot-dns-route53" # ------------------ # DECLARE CONDA PATH # ------------------ export PATH="${HOME}/miniconda/bin:${PATH}" # --------------------------------------------------- # Check Mini(Conda) is Installed Otherwise Install It # --------------------------------------------------- checkforconda () { if conda 2>&1 | grep -i 'command not found'; then echo "${bold}${red}CONDA NOT FOUND - ${bold}${green}Installing Mini(Conda)" export PATH="${HOME}/miniconda/bin:${PATH}" wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh bash miniconda.sh -b -p ${HOME}/miniconda hash -r conda config --set always_yes yes --set changeps1 no conda update -q conda sudo rm miniconda.sh echo "${bold}${green}CONDA INSTALLED - Continuing" else echo "${bold}${green}CONDA FOUND - Continuing" fi } checkforconda # ----------------------------------------- # Find which command line mailer we can use # ----------------------------------------- find_mail_binary() { local x= path= binary=$1 bin_paths='/bin /usr/bin /usr/local/bin /usr/sbin /usr/local/sbin /root/bin /root/.bin' for x in $bin_paths; do path="$x/$binary" if [ -x $path ]; then echo $path return fi done } # ------------------------------- # Set Conda Path and Update Conda # ------------------------------- printf '\n\n%s\n\n' "${bold}${magenta}Updating Conda" conda update -q conda # ------------------------------------------------- # Make sure we always run the latest Python version # ------------------------------------------------- conda update python # ----------------------------------------------------------- # Check for Existing Environment otherwise Create Environment # ----------------------------------------------------------- DIR="${HOME}/miniconda/envs/${environmentname}" if [ -d "${DIR}" ]; then printf '\n%s\n%s\n\n' "${bold}${cyan}Environment ${DIR} Found" "Continuing with Renewals" else printf '\n%s\n%s\n\n' "${bold}${red}Environment ${DIR} Not Found" "${bold}${yellow}Creating Environment" conda create -q -n ${environmentname} python="${pythonversion}" fi # -------------------- # Activate Environment # -------------------- printf '\n%s\n\n' "${bold}${magenta}Activating Environment" source activate ${environmentname} # --------------------------------- # Upgrade / Install Certbot and Pip # --------------------------------- printf '\n%s\n\n' "${bold}${magenta}Upgrading PIP" pip install --upgrade pip printf '\n%s\n\n' "${bold}${magenta}Installing / Upgrading Certbot" pip install certbot --upgrade # --------------------------- # Install Certbot DNS Plugins # --------------------------- if [ ${cloudflare_dns} -eq 1 ] then echo "${bold}${magenta}Installing Certbot Cloudflare DNS Plugin" pip install ${cloudflare} --upgrade else : fi if [ ${cloudxns_dns} -eq 1 ] then echo "${bold}${magenta}Installing Certbot CloudXNS DNS Plugin" pip install ${cloudxns} --upgrade else : fi if [ ${digitalocean_dns} -eq 1 ] then echo "${bold}${magenta}Installing Certbot DigitalOcean DNS Plugin" pip install ${digitalocean} --upgrade else : fi if [ ${dnsimple_dns} -eq 1 ] then echo "${bold}${magenta}Installing Certbot DNSSimple DNS Plugin" pip install ${dnsimple} --upgrade else : fi if [ ${dnsmadeeasy_dns} -eq 1 ] then echo "${bold}${magenta}Installing Certbot DNS Made Easy DNS Plugin" pip install ${dnsmadeeasy} --upgrade else : fi if [ ${google_dns} -eq 1 ] then echo "${bold}${magenta}Installing Certbot Google DNS Plugin" pip install ${google} --upgrade else : fi if [ ${linode_dns} -eq 1 ] then echo "${bold}${magenta}Installing Certbot Linode DNS Plugin" pip install ${linode} --upgrade else : fi if [ ${luadns_dns} -eq 1 ] then echo "${bold}${magenta}Installing Certbot LUA DNS Plugin" pip install ${luadns} --upgrade else : fi if [ ${nsone_dns} -eq 1 ] then echo "${bold}${magenta}Installing Certbot NSOne DNS Plugin" pip install ${nsone} --upgrade else : fi if [ ${ovh_dns} -eq 1 ] then echo "${bold}${magenta}Installing Certbot OVH DNS Plugin" pip install ${ovh} --upgrade else : fi if [ ${rfc2136_dns} -eq 1 ] then echo "${bold}${magenta}Installing Certbot RFC2136 BIND DNS Plugin" pip install ${rfc2136} --upgrade else : fi if [ ${route53_dns} -eq 1 ] then echo "${bold}${magenta}Installing Certbot Route53 DNS Plugin" pip install ${route53} --upgrade else : fi # ------------------------------------ # Show Python and Certbot Version Info # ------------------------------------ python -VV certbot --version # ------------------------------------------------------ # Capture Plugin Info and Append this to the renewal log # ------------------------------------------------------ versionoutput=$(certbot plugins) # ---------------------------------------------------------------------------- # Run our Certbot Renew Command and Send the Output to our variable "cboutput" # ---------------------------------------------------------------------------- certbotoutput=$(certbot renew --renew-hook "${renewhook}") # -------------------------------------------------------------------------- # If The Command Returned Output Then Send a Mail and Append to our Log File # -------------------------------------------------------------------------- if [[ ! -z "${certbotoutput}" ]]; then if [ -n $(find_mail_binary mail) ]; then echo "${certbotoutput} ${versionoutput}" | mail -s "${emailsubject}" ${recipient} echo "${certbotoutput}" >> ${renewallogfile} else echo "${bold}${red}WARN: missing mail command" fi else echo "${bold}${yellow}INFO: no output was received from certbot" fi # ---------------------- # Deactivate Environment # ---------------------- conda deactivate # --------------- # Exit with Error # --------------- exit ${?}