Einige Links auf dieser Seite sind Affiliate-Links: bei einer Anmeldung darüber erhält diese Seite eine Provision, für Sie ohne Mehrkosten. Preise und Empfehlungen bleiben davon unberührt.
Vier Websites auf einer Legacy-Box gingen dunkel. Nicht langsam, nicht 500 - dunkel, im Sinne von: der TLS-Handshake scheiterte, bevor überhaupt ein Request existierte. Sie waren fünf Tage down, bevor es jemandem aufgefallen ist, weil niemand das eine Ding beobachtet hat, das zählte.
Die Ursache war banal: Die Zertifikatserneuerung lief in einem Control Panel, die Lizenz des Panels lief aus, und sein täglicher Renewal-Cron starb mit ihr. Sieben weitere Zertifikate auf derselben Maschine waren so terminiert, dass sie in den folgenden drei Wochen ablaufen würden.
Die Distro war Jahre über ihr End of Life hinaus, also ließ sich certbot nicht saubereinstallieren, und die Vendor-Pakete waren längst weg. Das ist das Rezept, das es in unter dreißig Minuten behoben hat, ohne eine einzige vhost-Datei anzufassen, und das Monitoring, das dafür sorgt, dass es nicht wieder lautlos passieren kann.
Warum acme.sh und nicht certbot
certbot ist eine Python-Anwendung mit einem Abhängigkeitsbaum. Auf einer aktuellen Distro ist das kein Problem. Auf einer End-of-Life-Distro ist es das ganze Problem: Das System-Python ist zu alt für moderne cryptography-Wheels, pip will einen Compiler, den man auf einer Produktionsbox nicht haben will, und der Snap-Weg braucht einen Daemon, den die Distro nie mitgeliefert hat.
acme.sh ist ein Shell-Skript. Es braucht bash und entweder curl oder wget. Das ist die gesamte Abhängigkeitsliste. Es spricht per curl über HTTPS mit der ACME-API, also ist selbst ein uraltes OpenSSL nicht im Signierpfad, und bash 4.2 ist neu genug.
Es gibt auch eine angenehme Ironie, die man kennen sollte, wenn man aus einem Panel aussteigt: Mehrere kommerzielle Panels nutzen intern acme.sh. Man ersetzt nicht den Mechanismus, man holt ihn nur aus etwas heraus, das aufgehört hat zu laufen.
Installieren, und die Standard-CA korrigieren
curl https://get.acme.sh | sh -s email=you@yourdomain.dev
source ~/.bashrc
Dann sofort den Schritt machen, den die meisten Anleitungen überspringen. Seit Version 3 nutzt acme.sh standardmäßig ZeroSSL, nicht Let’s Encrypt, und ZeroSSL verlangt eine Kontoregistrierung mit EAB-Zugangsdaten. Wenn du Let’s Encrypt willst, sag es einmal:
acme.sh --set-default-ca --server letsencrypt
acme.sh --register-account -m you@yourdomain.dev
Überspringst du das, scheitert dein erster Issue-Versuch mit einem Registrierungsfehler, der wie ein Netzwerkproblem aussieht.
Ausstellen, ohne die vhosts anzufassen
Das Ziel ist null Änderungen an der Webserver-Konfiguration, denn auf einer Box wie dieser werden die Configs von etwas generiert, das man nicht mehr unter Kontrolle hat, und eine manuelle Bearbeitung wird überschrieben.
Zwei Dinge machen das möglich.
Erstens: Webroot-Validierung in das Verzeichnis nutzen, das der Server bereits ausliefert. Zuerst prüfen, ob schon ein Pfad verdrahtet ist - Panels lassen oft einen zurück:
curl -s -o /dev/null -w '%{http_code}\n' \
http://yourdomain.dev/.well-known/acme-challenge/test
Alles außer einem Verbindungsfehler bedeutet, der Pfad ist erreichbar, und man muss nur noch herausfinden, auf welches Verzeichnis auf der Platte er zeigt. Dann:
acme.sh --issue -d yourdomain.dev -d www.yourdomain.dev \
-w /var/www/letsencrypt
Zweitens: das Zertifikat unter den Dateinamen ablegen, die der Server bereits erwartet. Nicht die Config auf das Verzeichnis von acme.sh zeigen lassen, sondern acme.sh auf die Dateinamen der Config zeigen lassen. --install-cert macht genau das, und der Reload-Hook macht es live:
acme.sh --install-cert -d yourdomain.dev \
--fullchain-file /etc/ssl/sites/yourdomain.dev.crt \
--key-file /etc/ssl/sites/yourdomain.dev.key \
--reloadcmd "nginx -s reload && apachectl graceful"
Der Inhalt der Dateien ändert sich, die Pfade nie, und nichts in der Webserver-Konfiguration wird bearbeitet.
Abgelaufene Zertifikate blockieren die Erneuerung nicht
Die naheliegende Sorge: Jede Website leitet HTTP auf HTTPS um, und HTTPS ist kaputt - wie erreicht die Validierung dann die Challenge-Datei?
Es funktioniert. Let’s Encrypt folgt dem Redirect auf HTTPS und validiert dabei das Zertifikat nicht während der HTTP-01-Challenge. Ein lange abgelaufenes Zertifikat, ein selbstsigniertes, ein Hostname-Mismatch - nichts davon blockiert die Ausstellung. Man muss die Redirects nicht vorher auflösen, was gut ist, denn bei einer generierten Config könnte man es ohnehin nicht.
Automatisieren, dann auf einen Konkurrenten prüfen
acme.sh --install-cronjob
crontab -l | grep acme
Diese zweite Zeile ist die wichtige, und es ist der Fehler, den wir gemacht haben. Der Renewal-Cron des Panels war deaktiviert - und ein zweiter Panel-Cron, der denselben Job zu einer anderen Stunde machte, saß vier Zeilen weiter unten, beim ersten Durchgang übersehen. Zwei Erneuerungsmechanismen, die um dieselben Dateien wettrennen, ist ein Problem, das Wochen später als ein Zertifikat auftaucht, das auf der Platte gültig und im Speicher des Servers abgelaufen ist.
Die ganze Crontab lesen. Auskommentieren, nicht löschen, damit man sehen kann, was früher da war:
crontab -l > /root/crontab.bak-$(date +%Y%m%d)
acme.sh erneuert standardmäßig nach 60 Tagen, also hat ein fehlgeschlagener Lauf 30 Tage Retries, bevor überhaupt etwas für Nutzer sichtbar wird. Das ist ein großzügiger Puffer und genau der Grund, warum ein stiller Fehler monatelang laufen kann.
Das ausgelieferte Zertifikat überwachen, nicht die Datei auf der Platte
Das ist der Teil, der den ursprünglichen Ausfall abgefangen hätte, und den fast niemand einrichtet.
Die Datei zu prüfen reicht nicht: Sie sagt dir, dass die Erneuerung etwas geschrieben hat, nicht, dass der laufende Prozess es übernommen hat. Ein Reload, der lautlos fehlgeschlagen ist, lässt eine frische Datei auf der Platte und ein abgelaufenes Zertifikat im Speicher zurück. Den Handshake prüfen:
#!/usr/bin/env bash
# days until the served certificate expires, per domain
for d in "$@"; do
end=$(echo | openssl s_client -servername "$d" -connect "$d":443 2>/dev/null \
| openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
if [ -z "$end" ]; then echo "CRITICAL $d: no TLS handshake"; continue; fi
days=$(( ( $(date -d "$end" +%s) - $(date +%s) ) / 86400 ))
[ "$days" -lt 25 ] && echo "WARN $d: $days days left" || echo "OK $d: $days days"
done
Täglich über jeden Hostnamen laufen lassen, den die Box ausliefert, und unter 25 Tagen Alarm schlagen. Zwei Eigenschaften machen das zur richtigen Prüfung: Sie schlägt laut Alarm, wenn der Handshake überhaupt fehlschlägt, was der eigentliche Ausfall ist, und weil sie liest, was der Server einem Client gibt, kann sich ein Reload, der nicht passiert ist, nicht hinter einer erneuerten Datei verstecken.
Einen Tail des Renewal-Logs im selben Job dazu, und man hat die ganze Geschichte in einem Alert:
tail -n 5 /var/log/acme-renew.log
Das Ergebnis
Elf Zertifikate in einem Durchgang neu ausgestellt, alle für die nächsten drei Monate gültig, vier tote Websites wieder oben, Erneuerung in der Hand eines 200-KB-Shell-Skripts mit einem Cron-Eintrag, den wir lesen können. Keine vhost-Datei wurde verändert; nur die Bytes innerhalb der Zertifikatsdateien haben sich geändert.
Die größere Lektion hat nichts mit ACME-Clients zu tun. Sie ist, dass der Ablauf einer Sache nicht der Fehler ist; die Stille ist es. Die Erneuerung war etwa zwei Monate kaputt, bevor die erste Website ausfiel, und der einzige Grund, warum es jemand herausgefunden hat, war eine Website, die vor den Augen der Nutzer dunkel wurde. Eine zwanzigzeilige Handshake-Prüfung hätte es in der ersten Woche angezeigt.
Wenn du eine neue Box aufsetzt statt eine alte zu retten: Caddy macht das alles ohne jede Konfiguration - diesen Stack haben wir in Caddy und Docker im Produktivbetrieb aufgeschrieben. Dieses Rezept ist für die Maschine, die man geerbt hat, bei der die Config generiert wird, die Distro eingefroren ist, und die einzig akzeptable Änderung die kleinstmögliche ist.