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