Just add water

Since letsencrypt has made it easy to actually get the little green lock icon in all the browser, I've deployed it nearly everwhere, where reloading keys every three months is not an issue (looking at you, dovecot and ejabberd). When using FreeBSD, the security/dehydrated port has made things smooth enough for me not to be to afraid to execute it from a periodic script: It only requires bash and curl and can be executed as non-privileged user.

So here's a step by step instruction how to properly set it up:

  1. Install the port/package:

    pkg install dehydrated
  2. Create the letsencrypt user, for example:

    echo letsencrypt::::::::/bin/sh: | adduser -w random -f -
  3. Create your config copy /usr/local/etc/dehydrated/config by duplicating the example:

    cp /usr/local/etc/dehydrated/config.example /usr/local/etc/dehydrated/config
  4. Edit /usr/local/etc/dehydrated/config so it reads CONTACT_EMAIL=me@foo.com. (Don't forget to remove the # at the line's start.)

  5. By default, dehydrated's work dir is /usr/local/etc/dehydrated. I do not like that, because the letsencrypt user needs write access to that directory for its housekeeping files and could modify things like the config and – worse – the deploy.sh script. So I create a different work dir:

    mkdir /var/dehydrated
    chown -R letsencrypt /var/dehydrated
  6. And then I change /usr/local/etc/dehydrated/config to read BASEDIR=/var/dehydrated. (Again, don't forget to un-comment the line.)

  7. The web directory for challenge replies defaults to /usr/local/www/dehydrated. It needs to be writable by letsencrypt user:

    chown -R letsencrypt /usr/local/www/dehydrated
  8. Configure domains.txt:

    echo 'foo.com www.foo.com' > /var/dehydrated/domains.txt
  9. I want dehydrated to be run weekly by periodic, I also setup the deploy script (see below). I put those lines in /etc/periodic.conf:

    weekly_dehydrated_enable="YES"
    weekly_dehydrated_user="letsencrypt"
    weekly_dehydrated_deployscript="/usr/local/etc/dehydrated/deploy.sh"
  10. The deploy.sh script needs to be setup, it will tell all frontends to reload certs. For my nginx installations, it is enough to put this into /usr/local/etc/dehydrated/deploy.sh:

    #!/bin/sh
    
    /usr/sbin/service nginx reload
  11. Don't forget execute permissions:

    chmod +x /usr/local/etc/dehydrated/deploy.sh
  12. Finally, for nginx to correctly route requests to the web dir, add this to your server block. Don't forget to enable listen 80:

    location /.well-known/acme-challenge/ {
        alias /usr/local/www/dehydrated/;
    }
  13. Before running dehydrated for the first time, you should reload your nginx config. This also is an implicit check for correct permissions on deploy.sh ;):

    /usr/local/etc/dehydrated/deploy.sh
  14. Run dehydrated to set up and agree to terms and conditions:

    su letsencrypt -c 'dehydrated --register --accept-terms'
  15. Then run it again to actually do a challenge/response and generate certs:

    su letsencrypt -c 'dehydrated -c'
  16. If everything went fine, tell nginx to use the new certs in your server block. Don't forget to enable listen 443 ssl:

    ssl_certificate /var/dehydrated/certs/www.foo.com/fullchain.pem;
    ssl_certificate_key /var/dehydrated/certs/www.foo.com/privkey.pem;
  17. Make nginx use your new certs:

    /usr/local/etc/dehydrated/deploy.sh

You should be able to see your web site with a little green lock icon now, carrying a letsencrypt cert.

In order to verify that all your setups have been setup correctly, I wrote a script that checks them all:

HOSTS="mail.foo.com:25:smtp mail.foo.com:imaps www.foo.com jabber.foo.com:5232"
unset LANG LC_CTYPE LC_MESSAGES LC_TIME

for host in $HOSTS; do
  unset starttls
  [ ${host%:*} = ${host} ] && host=${host}.:443
  if [ ${host%*:*:*} != ${host} ]; then
    starttls="-starttls ${host#*:*:} "
    host=${host%:*}
  fi
  echo $host ${starttls}
  notafter=$( yes q | openssl s_client -servername ${host} -connect ${host} ${starttls} 2>/dev/null | openssl x509 -noout -enddate | grep ^notAfter= | cut -d = -f 2- )
  secs=$( date -j -f "%b %d %T %Y %Z" "${notafter}" +%s )
  now=$( date +%s )
  printf "% 4d days .. until %s\n" $(( (secs - now) / 86400 )) "${notafter}"
done