WhyOne wildcard certificate, obtained from Let’s Encrypt and renewed automatically, serves every TLS service on the host — web, mail, IMAP, LDAP, XMPP, database. The renewal runs as an unprivileged user with a DNS key that can write exactly one record, and services are reloaded by a root cron job the renewal user can only signal, never command.

See also

Design

Validation uses the dns-01 challenge, which works for wildcard names and needs no service on port 80. The naive form — letting the ACME client update the production zones — would hand an automated, internet-facing process a key to every zone. Instead the challenges live in a dedicated dynamic zone, and every real domain delegates only its challenge name into it with a CNAME. The production zones stay static files; the update key can create one TXT record in one throwaway zone and nothing else.

DNS side

The challenge zone is dynamic, and its update policy grants the key a single name, a single type:

zone "Challenge zone" {
    type master;
    file "dynamic/Challenge zone";
    update-policy {
        grant "cert-update" name _acme-challenge.Challenge zone TXT;
    };
};
include "cert-update.key";

Each certificate domain points its challenge name into that zone — one line per zone file, and the only ACME-related line the static zones ever carry:

_acme-challenge	IN	CNAME	_acme-challenge.Challenge zone.

The TSIG key is generated with tsig-keygen cert-update. Two readers need it and root is neither: the named daemon re-reads it on every rndc reload, and the ACME user presents it through nsupdate. Owner for one, group for the other, nothing for anyone else:

Key readable by its two consumers only
chown bind:ACME user cert-update.key
chmod 640 cert-update.key

Client side

The client is acme.sh, running as a dedicated unprivileged user whose home doubles as the certificate store (/var/db/acme, mode 750). Its account configuration names the dns_nsupdate validation hook and the pieces above:

NSUPDATE_SERVER='Primary nameserver'
NSUPDATE_KEY='/usr/local/etc/namedb/cert-update.key'
NSUPDATE_ZONE='Challenge zone'

Issuing then names every domain and SAN the certificate must carry; installation copies the result to the shared certificate location the services read (see the Apache page for the key’s group-based permissions, which survive renewal because acme.sh writes through the existing file):

Issue and install (once; renewals reuse the recorded parameters)
acme.sh --issue --dns dns_nsupdate -d Domain -d 'Wildcard'
acme.sh --install-cert -d Domain \
        --cert-file      /usr/local/etc/cert/letsencrypt/ALL.Domain.crt \
        --key-file       /usr/local/etc/cert/letsencrypt/ALL.Domain.key \
        --ca-file        /usr/local/etc/cert/letsencrypt/ca.pem \
        --fullchain-file /usr/local/etc/cert/letsencrypt/fullchain.pem \
        --reloadcmd      "touch /var/db/acme/cert-renewed"

Renewal and service reload

A daily cron entry, owned by the ACME user, renews whatever is due:

17 22 * * * /usr/local/sbin/acme.sh --cron --home /var/db/acme/.acme.sh > /dev/null

The unprivileged user cannot — and should not — restart the services that hold the old certificate in memory. The reloadcmd above only drops a watchfile inside the ACME user’s own directory; a root cron job checks for it and does the restarts. The watchfile must not live in a world-writable place like /tmp, where any local user could create it and trigger the nightly restart of every TLS service.

0 23 * * * /root/cron/reload-cert-services
#!/bin/sh
WATCHFILE=/var/db/acme/cert-renewed
if [ -e "$WATCHFILE" ]; then
    service apache24 reload
    service postfix  restart
    # ... every service reading the shared certificate ...
    unlink -- "$WATCHFILE"
fi