How to install low dependency Let's Encrypt client

Problem statement

The certbot client – a regular ACME-client (client to interface with – is rather involved when it comes to dependencies: recent python, recent pip, crypto bindings for python, etc.

As a result, installing certbot on a legacy system (e.g. openSUSE-13.2) is burdensome.

In this quick post I’m documenting for myself how to use a low-dependency variant that’s just as good1.

Discussion logo, I presume MIT license.

With a quick search, I have found as an alternative. It’s 2.3k of bash, and depends on: sudo, curl, openssl, sed, grep, awk, and mktemp.

In other words, it’s a low dependency dream come true.

On top of that, the features are just enough to comfortably integrate into any workflow, as there are hooks for every action imaginable:

deploy_challenge, clean_challenge, sync_cert, deploy_cert, deploy_ocsp, unchanged_cert, invalid_challenge, request_failure, generate_csr, startup_hook, exit_hook

What follows, then, is my way of integrating it to a fairly old Linux distro, with Apache serving on port 80.


Note: comments are inlined in the scripts below, but generally should be copy-pastable.

(What’s missing below are some Apache-specific config bits, but I’m leaving those as an exercise for the reader2…)

Btw, I opted for installation in /opt/dehydrated over the built-in support for system-standard directories3. Main reason: ease of backup. One dir to rule them all.

cd /opt

# Install
git clone
cd dehydrated
rm -rf .git* LICENSE CHANGELOG docs/

# Add dedicated user
useradd dehydrated -s /bin/false -d /opt/dehydrated -M

# Prepare docroot
mkdir -p /var/www/html/.well-known/acme-challenge
chmod a+rwx,o+t /var/www/html/.well-known/acme-challenge
# ↑ this could be restricted further to the dehydrated user

# Setup necessary directories
mkdir -p certs accounts chains tmp
chown dehydrated:root certs accounts chains tmp
chmod ug=rwx,o= certs accounts chains tmp

# Fetch current mozilla cert bundle (see CURL_OPTS in config)
curl -k > /opt/dehydrated/cacert.pem

# Configure
cat > config <<'EOF'
# for prod setup uncomment the following:
CURL_OPTS="--cacert /opt/dehydrated/cacert.pem"

# Add appropriate hook(s)
cat > <<'EOF'

deploy_cert() {
  local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}"
  local CHAINFILE="${5}" TIMESTAMP="${6}"

  # site-specific configuration here

  echo " + deploy hook successful. \\o/"

HANDLER="$1"; shift
if [[ "${HANDLER}" =~ ^(deploy_cert)$ ]]; then
  "$HANDLER" "$@"
chown dehydrated:root
chmod ug=rwx,o=

# Configure the domains we want certs for
echo "$(hostname -f) $(hostname -f)" > domains.txt

And then it’s just:

# Register
./dehydrated --register --accept-terms
# Renew certs (cron-like)
./dehydrated -c

For the cronjob I came up with:

cat > /usr/local/sbin/cronjob-refresh-letsencrypt <<'EOF'

trap 'rm -f "$TF"' EXIT

/opt/dehydrated/dehydrated -c > "$TF"
if [ $? -ne 0 ]; then
  echo "Renewing SSL certificate ended with: $?"
  cat "$TF"
chmod a+x /usr/local/sbin/cronjob-refresh-letsencrypt

because there doesn’t seem to be a way to easily silence the info notices.

Closing words

I like dehydrated a lot. Clocking at 2.3k lines of bash and only depending on standard components, it’s the perfect antidote to the certbot dependency hell.

Plus it’s well engineered to be extensible.

Also, by overriding the SSL cert bundle I avoided the bullet of trying to update the system-wide root of trust. Since every distro does that differently, this is a huge time-saver4.

  1. No, better. Because it’s trivial to integrate with other software using its hook mechanism.

  2. Think RewriteRule or RedirectMatch 307; and yes, acme workers from LE follow redirects.

  3. Think /usr/local, /etc, etc.

  4. And limits potential unintended consequences of that change.