Using centralised management with Lets Encrypt


Lets Encrypt is a service that provides free DV SSL Certificates. 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.

  1. 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.
  2. Install bind, then create your base zone file in /var/named/data/ for ‘‘ with something like:
    $ORIGIN .
    $TTL 21600      ; 6 hours          IN SOA (
                                    2016112648 ; serial
                                    43200      ; refresh (12 hours)
                                    3600       ; retry (1 hour)
                                    1209600    ; expire (2 weeks)
                                    3600       ; minimum (1 hour)
  3. 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.
  4. 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
  5. 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
            $NSUPDATE <<-EOF
                    update add ${2} ${TTL} in TXT "${4}"
            $NSUPDATE <<-EOF
                    update delete ${2} ${TTL} in TXT "${4}"
            /home/user/bin/deploy_cert "${1}" "${2}" "${3}" "${4}"
            # do nothing for now
            echo Unknown hook "${1}"
            exit 1
    exit 0
  6. 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.

  7. 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"`
            ssh $HOST $SSHOPTIONS \
    "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"`
            ssh $HOST $SSHOPTIONS \
    "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"`
            ssh $HOST $SSHOPTIONS \
    "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"`
            ssh $HOST $SSHOPTIONS \
    "cat << 'EOF' > $FULLCHAIN
    ## Do we have a command to run afterwards?
    if [ ! -z "$COMMAND" ]; then
            echo "$HOST - Executing remote command: $COMMAND"
            ssh -t $HOST $SSHOPTIONS "$COMMAND"
  8. 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.

  1. 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
  2. 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

  3 Responses to “Using centralised management with Lets Encrypt”

  1. Thanks for this great write-up! I was going to make an _acme-challenge subdomain for each subdomain to be certified (with NS and A records for each) but I like your CNAME approach better.

    Thanks for sharing!

    (Blog Format Comment: please put the date in your articles. It makes it difficult to know if it the info is outdated or not if its missing šŸ™‚

  2. Hmmm i am struggling to get this all working and keep getting the below error

    c0mputerking@certs:~$ dehydrated/dehydrated –cron –domain
    # INFO: Using main config file /home/c0mputerking/dehydrated/config
    + Signing domains…
    + Generating private key…
    + Generating signing request…
    + Requesting challenge for…
    + Responding to challenge for…
    Unknown hook invalid_challenge

    Not much to go on i know but i do get some stuff about updates in my syslog about bind being updated so some things are happening

    Jun 14 15:03:44 certs named[958]: client nsupdate-key: signer “computerking-ca” approved
    Jun 14 15:03:44 certs named[958]: client nsupdate-key: updating zone ‘’: adding an RR at ‘’ TXT
    Jun 14 15:04:07 certs named[958]: client nsupdate-key: signer “computerking-ca” approved
    Jun 14 15:04:07 certs named[958]: client nsupdate-key: updating zone ‘’: deleting an RR at TXT

 Leave a Reply

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>