Using centralised management with Lets Encrypt

Since StartSSL had issues and are being delisted, I needed an alternative.

The one thing that put me off Lets Encrypt for so long is that I could no longer administer all my certs from a central location. This meant running software on several systems to keep the certs updated - or manual intervention every 90 days.

This wasn't acceptable to me - so I hunted for another solution. Enter the DNS-01 validation challenge. The Lets Encrypt recommended solution (certbot) doesn't support the DNS-01 method yet - so I needed something else.

This solution is aimed at those who have your own domain, and have many hosts that you use certs on (could be anything from SQL servers, to mail, to web servers) and want to manage them all from the same place - and not run additional software on them. In this guide, I've used '' as the domain name. Adjust to suit.

Set up your VM to use for the cert management. As always, I used Scientific Linux 7. You can use whatever you like, just adjust accordingly.

Install bind, then create your base zone file in /var/named/data/ for '' with something like:

$TTL 21600      ; 6 hours  IN SOA (
    2016112648 ; serial
    43200      ; refresh (12 hours)
    3600       ; retry (1 hour)
    1209600    ; expire (2 weeks)
    3600       ; minimum (1 hour)

Set up bind and dynamic DNS updates with 'nsupdate'. There is lots written on this topic that is covered in much more detail that I could cram into here. I like the guide nsupdate: Painless Dynamic DNS.

Now we set up the LE software. I used 'dehydrated' (previously You'll want to check this out as a non-root user.

# git clone

I wrote a hook file that gets called from dehydrated while it goes through its process. Save it to the directory you cloned dehydrated into under the filename ''

#!/usr/bin/env bash

# Example how to deploy a DNS challenge using nsupdate

set -e
set -u
set -o pipefail

NSUPDATE="/usr/bin/nsupdate -k /home/user/Knsupdate-key.+157+17276.key"

case "$1" in
        update add ${2} ${TTL} in TXT "${4}"
        update delete ${2} ${TTL} in TXT "${4}"
    /home/user/bin/deploy_cert "${1}" "${2}" "${3}" "${4}"
    echo "Nothing to do for hook ${1}"
    exit 0

exit 0

Configure dehydrated by coping the example config file in docs/examples, and set:


Once you have verified that all these steps work, you can comment out the CA line to obtain live certs.

Now we create the deployment script called from as ~/bin/deploy_cert:

set -e

SSHOPTIONS="-o ControlMaster=auto -o ControlPersist=60 -o ControlPath=~/.ssh/%r@%h-%p"

## Do we have a config file for this host?
if [ -f $HOME/deployment/$2 ]; then
    . $HOME/deployment/$2
    echo "No config file for host $host"
    echo "Aborting..."
    exit 0;

echo "Starting deployment to $HOST"

## Connect to the remote server and leave session open for max 60 seconds.
ssh $HOST $SSHOPTIONS "/bin/true"

## Do we have a cert to deploy?
if [ -f "$HOME/dehydrated/certs/$2/cert.pem" ] && [ ! -z "$CERT" ]; then
    echo "$HOST - Copying CERT to $CERT"
    CERT_FILE=`cat "$HOME/dehydrated/certs/$2/cert.pem"`
"cat << 'EOF' > $CERT

## Do we have a key to deploy?
if [ -f "$HOME/dehydrated/certs/$2/privkey.pem" ] && [ ! -z "$KEY" ]; then
    echo "$HOST - Copying KEY to $KEY"
    KEY_FILE=`cat "$HOME/dehydrated/certs/$2/privkey.pem"`
"cat << 'EOF' > $KEY

## Do we have a chain to deploy?
if [ -f "$HOME/dehydrated/certs/$2/chain.pem" ] && [ ! -z "$CHAIN" ]; then
    echo "$HOST - Copying CHAIN to $CHAIN"
    CHAIN_FILE=`cat "$HOME/dehydrated/certs/$2/chain.pem"`
"cat << 'EOF' > $CHAIN

## Do we have a fullchain to deploy?
if [ -f "$HOME/dehydrated/certs/$2/fullchain.pem" ] && [ ! -z "$FULLCHAIN" ]; then
    echo "$HOST - Copying FULLCHAIN to $FULLCHAIN"
    FULLCHAIN_FILE=`cat "$HOME/dehydrated/certs/$2/fullchain.pem"`
"cat << 'EOF' > $FULLCHAIN

## Do we have a command to run afterwards?
if [ ! -z "$COMMAND" ]; then
    echo "$HOST - Executing remote command: $COMMAND"

Create the directory $HOME/deployment and create a text file (for example)
COMMAND="sudo service httpd reload"

Within this file CERT, KEY and CHAIN are the files that hold your SSL cert on the server. HOST is the SSH server that hosts your web site. COMMAND is the command run after the cert is copied across. It is expected that you be able to set up SSH keys to do this without entering a password to allow automatic updates of your certs.

Now we turn our attention to the main DNS server that serves your domain to the public.

In your main DNS server for, we now want to delegate the entire '' namespace to the DNS server we just created. Don't forget to also set an IP for the certvm system. This will look something like:

certvm        IN        A
le            IN        NS

Next, for each fqdn we want to create a certificate for, we want to create a CNAME back to the namespace - for example:

_acme-challenge.www        IN        CNAME
_acme-challenge.sslserver  IN        CNAME

You should now be ready to run dehydrated for the first time. After verifying that your setup works, remember to remove the CA line from the dehydrated config file. Feel free to leave feedback on this guide - I wrote it up mostly from memory - so I may well have missed a step.


  • Dec 2016: Updated dns-hook script to use EOF instead of printf - suggested by TCM @ Lets Encrypt Community

  • Jan 2019: Updated dns-hook script to not exit with code 1 on unknown hooks. Just print that there's nothing to do instead.


Comments powered by Disqus