Fully automate SSL/TLS certificate renewal with CPanel

Scenario

You are using a provider such as Arvixe which provides CPanel. You are a bit bored with manually renewing the certificate every three months and would like to automate it.

We will:

  1. check if the certificate(s) expires and, if yes;
  2. issue new certificates
  3. upload them to arvixe using cpanel interface

Assumptions and pre-requisites

  • This assumes to have access to a *nix box from where you’ll manage your certificates and upload them onto CPanel.
  • ssh access to your remote website
  • use certbot
  • you already have one or several sites and certificates
  • you have downloaded sslic.php from  https://github.com/neurobin/sslic

Overview

Here is the certbot command we will use:

certbot certonly --non-interactive --staple-ocsp  --email youremail@example.org -d yourserver.com -d www.yourserver.com --agree-tos --manual --manual-auth-hook /path/toyour/scripts/letsencryptauth.sh --manual-cleanup-hook /path/toyour/scripts/letsencryptclean.sh

  • non-interactive, email and agree-tos: This is to be used in a script so it has to be non-interactive.
  • d: list all your domains here, add as many -d as you need.
  • manual-auth-hook and manual-cleanup-hook: Let’s Encrypt will need to verify the ownership of your sites, those scripts will automate the upload and deletion of the authorisation files.
  • you already have set up ssh keys on the remote server so you don’t need to type a password to log in from the machine where the script is to your remote server.
  • I also use –redirect –hsts –uir, however, I am not sure they are effective and would welcome feedback on those.

We will, therefore, use three shell scripts:

  1. certupdate.sh: the main script checking the validity of the certificates, generating the new certificates and uploading them to cpanel
  2. letsencryptauth.sh: to upload the authorisation files.
  3. letsencryptclean.sh: to clean up.

certupdate.sh

#!/bin/bash

RENEW=22

echo "======================================="
date '+%a %d %b %y %H:%M:%S'
echo "============Starting script============"
#downloading let's encrypt CA file
wget -O /tmp/letsencryptCA.crt https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem.txt

#
#checking yourserver.com
#add '-servername yourserver.com' to use SNI in case you are on a shared server and ensure your certificate is gathered instead of the main cert.
#
certificate=$(echo |openssl s_client -servername yourserver.com -connect yourserver.com:443 2>/tmp/cert.tmp|openssl x509 -checkend $[86400 * $RENEW] -enddate)
  if [ "$certificate" == "" ]; then
    echo "Error: unable to check certificate"
  else
    if [[ $certificate =~ (.*)Certificate\ will\ expire ]]; then
    echo $certificate
    echo "certificate needs to be renewed"

    #
    #cert generation for yourserver.com and www.yourserver.com
    #
    certbot certonly --non-interactive --staple-ocsp --email email@example.org -d yourserver.com -d www.yourserver.com --agree-tos --manual --manual-auth-hook /path/toyour/scripts/letsencryptauth.sh --manual-cleanup-hook /path/toyour/scripts/letsencryptclean.sh
    echo "yourserver.com cert process completed, now uploading it to CPanel"

                #upload to cpanel. Only one upload as www and . share one cert for my configuration. You may want to change that to your own config.
                USER='cpanel username' PASS='cpanelpassword' EMAIL='email@example.org' /usr/bin/php /path/toyour/scripts/sslic.php yourserver.com /etc/letsencrypt/live/yourserver.com/cert.pem /etc/letsencrypt/live/yourserver.com/privkey.pem /tmp/letsencryptCA.crt
                echo "yourserver.com upload to cpanel process complete"


  else
    echo $certificate
    echo "cert does not need to be renewed"
  fi
fi
echo "==================================="
date '+%a %d %b %y %H:%M:%S'
echo "============Script ends============"

RENEW=22 means that if the certificate is due to expire in 22 days, then we update it. Change this value as you wish.

letsencryptauth.sh

I call this script from certupdate.sh which I run as root/sudo, but my ssh key is under a normal user (non-root), so I call scp from another user.

#!/bin/bash

case "$CERTBOT_DOMAIN" in
"www.yourserver.com")
  echo $CERTBOT_VALIDATION >/tmp/$CERTBOT_TOKEN
  /usr/bin/sudo -u username /usr/bin/scp /tmp/$CERTBOT_TOKEN username@yourserver.com:/path/tosite/www/.well-known/acme-challenge
  ;;
"yourserver.com")
  echo $CERTBOT_VALIDATION >/tmp/$CERTBOT_TOKEN
  /usr/bin/sudo -u username /usr/bin/scp /tmp/$CERTBOT_TOKEN username@yourserver.com:/path/tosite/www/.well-known/acme-challenge
        ;;
"testcert.yourserver.com")
  echo $CERTBOT_VALIDATION >/tmp/$CERTBOT_TOKEN
  usr/bin/sudo -u username /usr/bin/scp /tmp/$CERTBOT_TOKEN username@yourserver.com:/path/tosite/testcert.yourserver.com/.well-known/acme-challenge
  ;;
esac

certbot passes the variables $CERTBOT_DOMAIN, $CERTBOT_VALIDATION and $CERTBOT_TOKEN to the script so you just have to add as many many “example.org”) sites you want. See certbot documentation for more details.

As you can see I have added a line for a third domain, testcert.yourserver.com, this is just to show that you can add as many cases as you want. You’ll need to modify certupdate.sh accordingly – or run separate certupdate_01.sh, certupdate_01.sh, etc, that each call letsencryptauth.sh.

Modify paths, domains and username to match your configuration.

letsencryptclean.sh

#!/bin/bash

case "$CERTBOT_DOMAIN" in
"www.yourserver.com")
  /usr/bin/sudo -u username /usr/bin/ssh -t username@yourserver.com "rm -vf /path/tosite/www/.well-known/acme-challenge/*"
  ;;
"yourserver.com")
  /usr/bin/sudo -u username /usr/bin/ssh -t username@yourserver.com "rm -vf /path/tosite/www/.well-known/acme-challenge/*"
  ;;
"testcert.yourserver.com")
  /usr/bin/sudo -u username /usr/bin/ssh -t username@yourserver.com "rm -vf /path/tosite/testcert.yourserver.com/.well-known/acme-challenge/*"
  ;;
esac

sslic.php

// Define the API call.
$cpanel_host = 'localhost';
$request_uri = "https://$cpanel_host:2083/execute/SSL/install_ssl";

modify the values to match your own config, for instance, mine looks like that:

// Define the API call.
$cpanel_host = 'dallas123.arvixeshared.com';
$request_uri = "https://$cpanel_host:2083/execute/SSL/install_ssl";

Crontab

As certupdate.sh has your cpanel login and password, you have to make sure it is in a safe location, at a minimum make sure that only the user running the script can read it.

50 15 * * WED /path/toyour/scripts/certupdate.sh>>/path/toyour/log/certupdate.log

This runs the script weekly, every Wednesday at 15:50.

To-do

  • test –redirect –hsts –uir
  • follow-up on must-staple support by Let’s Encrypt.
  • support of DNS CAA by Arvixe: when?
  • full HSTS support, not a dirty WordPress only 301 redir: so far Arvixe say they don’t support it (and probably won’t…).

Update – 30APR2018

Arvixe support confirmed they don’t support DNS CAA for my product yet but they will support it when they’ll upgrade CPanel to a version above 11.66, currently I am on 11.62. No ETA though.

Arvie support provided me with a new .htaccess file and it indeed solves my problems.

The rules I was using was causing Safari to complain of multiple redirections.

RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

The rule they provided solved that.

RewriteEngine On
RewriteCond %{SERVER_PORT} 80
RewriteRule ^(.*)$ https://catelin.net/$1 [R,L]

Update – 24MAY2018

I finally decided to give Cloudfare a try. It didn’t improve anything for me, I guess my traffic is far too low to see any improvements. However, I now use their DNS and – among other things – that allowed me to set DNS CAA records.

Leave a Reply

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