This describes how to setup dehydrated on a FreeBSD system to have your Let's Encrypt certificates updated automatically. It works for single hostname and multidomain certificates only. For verification, the http-01 protocol is used. Apache serves both the live websites, as well as the verification challenges. With slight modifications, one can adopt this for other webservers as well.
Either install the package:
# pkg install dehydratedOr install the port, which I prefer since it allows you to deselect Build and/or install documentation, which I barely need on a server:
# cd /usr/ports/security/dehydrated # make install clean
www.your-domain.invalid www.your-other-domain.invalid webmail-your-other-domain.invalidThe first line denotes a single hostname certificate, the second entry a multidomain certificate.
HOOK="$BASEDIR/hook.sh" AUTO_CLEANUP="yes"
#!/usr/local/bin/bash RESTART_FLAG="$BASEDIR/restart_apache" sendemail() { subject="$1" (cat <<EOF; cat) | sendmail -t From: root To: root Subject: $subject EOF } lookup() { local domain="$1" local domains_txt="$BASEDIR/domains.txt" local error local ssldir local tokendir local tokenfile local mainname=`awk '{if (/(^| )'"$domain"'($| )/) { print $1; }}' "$domains_txt"` if [ -z "$mainname" ]; then error="Domain $domain not found in $domains_txt" (cat <<EOF; cat "$domains_txt") | sendemail "$error" Content of $domains_txt: EOF echo "$error" >&2 exit 1 fi tokendir="/www/$mainname/htdocs/.well-known/acme-challenge" if [ -z "$3" ]; then # clean challenge rm "$tokendir/$2" elif [ -z "$4" ]; then # deploy challenge tokenfile="$tokendir/$2" oldmask=`umask` umask 0026 mkdir -p "$tokendir" umask "$oldmask" echo "$3" > "$tokenfile" chmod 0644 "$tokenfile" else # deploy cert ssldir="/www/$mainname/ssl" cp "$2" "$ssldir/$mainname.key" cp "$3" "$ssldir/$mainname.crt" cp "$4" "$ssldir/$mainname.chain" touch "$RESTART_FLAG" fi } deploy_challenge() { local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}" lookup "$DOMAIN" "$TOKEN_FILENAME" "$TOKEN_VALUE" } clean_challenge() { local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}" lookup "$DOMAIN" "$TOKEN_FILENAME" } deploy_cert() { local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}" TIMESTAMP="${6}" lookup "$DOMAIN" "$KEYFILE" "$CERTFILE" "$CHAINFILE" } invalid_challenge() { local DOMAIN="${1}" RESPONSE="${2}" cat <<EOF | sendemail "Validation of $DOMAIN failed" Response: $RESPONSE EOF } request_failure() { local STATUSCODE="${1}" REASON="${2}" REQTYPE="${3}" HEADERS="${4}" cat <<EOF | sendemail "Request of $DOMAIN failed" Status code: $STATUSCODE Reason: $REASON Request type: $REQTYPE Headers: $HEADERS EOF } exit_hook() { if [ -f "$RESTART_FLAG" ]; then service apache24 graceful rm "$RESTART_FLAG" fi }This assumes that
www.your-other-domain.invalid webmail-your-other-domain.invalidthen challenges will be placed as text files in the directory /www/www.your-other-domain.invalid/htdocs/.well-known/acme-challenge. Afterwards, you will get three files
<VirtualHost :80> ServerName www.your-domain.invalid DocumentRoot /www/www.your-domain.invalid/htdocs RedirectMatch 301 ^/(?!.well-known/) https://www.your-domain.invalid/ <Directory /www/www.your-domain.invalid/htdocs> Require all granted </Directory> </VirtualHost> <VirtualHost :443> ServerName www.your-domain.invalid DocumentRoot /www/www.your-domain.invalid/htdocs <Directory /www/www.your-domain.invalid/htdocs> Require all granted </Directory> SSLEngine on SSLCertificateChainFile /www/www.your-domain.invalid/ssl/www.your-domain.invalid.chain SSLCertificateFile /www/www.your-domain.invalid/ssl/www.your-domain.invalid.crt SSLCertificateKeyFile /www/www.your-domain.invalid/ssl/www.your-domain.invalid.key </VirtualHost>
# chmod 0755 /usr/local/etc/dehydrated/hook.sh
weekly_dehydrated_enable="YES"
# /usr/local/bin/dehydrated --register --accept-terms
# /usr/local/etc/periodic/weekly/000.dehydrated