Vor zehn Jahren war ein TLS-Zertifikat ein Kostenfaktor, den man im Jahresbudget einplanen musste. 200 Euro für ein Wildcard-Zertifikat von Sectigo oder DigiCert — und dann am 23. Dezember das Ablaufdatum übersehen, weil der Erinnerungskalender im Büro hing und man selbst schon im Weihnachtsurlaub war. Ich bin zwei Mal an Heiligabend ins Rechenzentrum gefahren, weil das Monitoring auf einer HP-UX-Kiste ein abgelaufenes qa.example.com nicht gepiept hat. Heute, mit Let's Encrypt, kostet mich ein Zertifikat null Euro und exakt die Zeit, die ich für die Automation brauche. Einmal richtig eingerichtet, läuft es. Wenn es nicht läuft, liegst meistens an einer der zehn Sachen, die ich gleich aufzähle.
Dieser Artikel ist für Admins, die keine Lust mehr haben, Zertifikate manuell zu erneuern. Ich zeige dir drei Wege — certbot (den Standard), dehydrated (das Leichtgewicht) und Traefik (den Reverse-Proxy, der dir die Arbeit komplett abnimmt). Dazu die zehn häufigsten Renewal-Fehler, die mich schon Stunden gekostet haben, und ein Monitoring-Setup, das dich warnt, bevor dein Chef es tut.
Warum überhaupt automatisieren — und warum jetzt Wildcards?
Let's Encrypt-Zertifikate laufen 90 Tage. Das ist kein Bug, sondern Absicht — kurze Laufzeiten erzwingen Automation. Wer 90-Tage-Zertifikate manuell erneuert, hat die Kontrolle über sein Leben verloren. Ich hab's ein Quartal lang versucht, als Let's Encrypt 2016 frisch war. Nach dem dritten vergessenen Renewal war die Automation per Cronjob Pflicht.
Der Klassiker ist die HTTP-01-Challenge: certbot legt eine Datei in dein .well-known/acme-challenge/-Verzeichnis, Let's Encrypt ruft sie über Port 80 ab und bestätigt damit, dass dir der Server gehört. Funktioniert perfekt, solange dein Webserver von außen auf Port 80 erreichbar ist. Scheitert grandios, wenn du interne Hosts, Load-Balancer oder Services hast, die keinen öffentlichen Port 80 haben.
Dann gibt es noch die DNS-01-Challenge. Statt einer Datei legst du einen TXT-Record in deiner DNS-Zone an. Let's Encrypt fragt _acme-challenge.deinedomain.de ab, findet den TXT-Record und stellt das Zertifikat aus. Der entscheidende Vorteil: DNS-01 erlaubt Wildcard-Zertifikate. Ein Zertifikat für *.deinedomain.de, das auf beliebig vielen Subdomains funktioniert — ohne dass jedes Sub einzeln validiert werden muss. Für Kubernetes-Cluster, Testumgebungen mit dynamischen Subdomains oder interne Dienste, die nie das öffentliche Internet sehen, ist das die einzig sinnvolle Lösung.
Der Haken: Du brauchst einen DNS-Provider mit API — und einen API-Token, den du in der Hook speicherst. Mehr dazu gleich.
Weg 1: certbot — der Platzhirsch mit Ecken
certbot ist die EFF-Referenzimplementierung und auf den meisten Servern per apt install certbot verfügbar. Für Standard-Setups mit HTTP-01 auf einem nginx oder Apache gibt es kaum einen Grund, etwas anderes zu verwenden. Ein certbot --nginx -d example.com und das Ding läuft. Der Cronjob unter /etc/cron.d/certbot oder der systemd-Timer certbot.timer erledigen den Rest.
Richtig interessant wird es mit DNS-01 und einem Plugin. Für Cloudflare sieht das so aus:
# Cloudflare API-Token in /etc/letsencrypt/cloudflare.ini
# dns_cloudflare_api_token = DEIN_TOKEN_HIER
certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
-d "*.deinedomain.de" -d deinedomain.de \
--non-interactive --agree-tos \
-m admin@deinedomain.de
Das stellt ein Wildcard-Zertifikat plus das Apex-Zertifikat aus. Die Credentials-Datei gehört auf chmod 600, und der Token sollte nur die nötigsten Rechte haben — Zone:Zone:Read und Zone:DNS:Edit bei Cloudflare reichen.
Der Nachteil von certbot: Es ist ein Python-Monolith mit Abhängigkeiten, der auf schlanken Containern und Embedded-Systemen nervt. Dazu kommt, dass certbot standardmäßig das Cert in /etc/letsencrypt/live/ ablegt, die eigentlichen Dateien aber in /etc/letsencrypt/archive/ verwaltet und per Symlink verknüpft — ein durchdachtes System, bis du anfängst, Verzeichnisse rsyncen zu wollen. Ich habe schon mehrfach Zertifikate neu ausstellen müssen, weil ich versehentlich den Symlink kopiert habe statt die Datei dahinter.
Weg 2: dehydrated — wenn's nur ums Zertifikat geht
dehydrated ist ein Bash-Skript. Kein Python, keine Runtime, keine Abhängigkeiten außer curl, openssl und sed — das ist auf jeder halbwegs aktuellen Linux-Kiste vorhanden. Genau aus diesem Grund habe ich es auf meinem Raspberry-Pi-Cluster im Einsatz: 70 Zeilen Hook-Skript für den Hetzner-DNS-API, und das Ding erneuert vier Wildcard-Zertifikate für vier interne Subdomains, ohne dass ich jemals wieder daran denken muss.
Die Konfiguration ist minimal. In /etc/dehydrated/config:
CA="https://acme-v02.api.letsencrypt.org/directory"
CHALLENGETYPE="dns-01"
HOOK="/etc/dehydrated/hook.sh"
CERTDIR="/etc/dehydrated/certs"
CONTACT_EMAIL="admin@deinedomain.de"
In /etc/dehydrated/domains.txt listest du deine Domains:
deinedomain.de *.deinedomain.de
andere-domain.de *.andere-domain.de
Die Hook ist das Herzstück. dehydrated ruft sie mit deploy_challenge und clean_challenge auf. Innerhalb der Hook setzt du den TXT-Record per API-Aufruf und löscht ihn nach der Validierung wieder. Für einen Homelab-Admin, der fünf interne Dienste absichern will, ist das Setup in 30 Minuten erledigt — und läuft danach jahrelang ohne Wartung. Letzten Monat habe ich eine Kiste aus 2022 zurückgesetzt und das dehydrated-Setup von damals lief immer noch fehlerfrei. Das ist die Art von Software, die ich mag.
Der Nachteil: dehydrated hat keine Built-in-Integration mit Webservern. Du musst die Zertifikate selbst in deine nginx- oder Apache-Konfiguration einbinden und einen Reload triggern. Das Hook-Skript muss das erledigen. Wer ein echtes Fire-and-forget will, ist mit Traefik besser bedient.
Weg 3: Traefik — Zero-Config-TLS als Nebeneffekt
Traefik ist ein Reverse-Proxy, der als Sidecar in Docker- und Kubernetes-Umgebungen läuft. Was für unseren Kontext wirklich zählt: Traefik holt sich Let's-Encrypt-Zertifikate automatisch, sobald du einen neuen Service mit einem Host-Label anmeldest. Kein Cronjob, kein Hook-Skript, kein manueller Reload. Du startest Traefik einmal mit einem Certificate-Resolver, und jedes weitere Deployment kriegt TLS geschenkt.
Minimales Traefik-Setup mit Docker und Cloudflare-DNS-01:
# docker-compose.yml (Auszug)
services:
traefik:
image: traefik:v3.2
command:
- "--certificatesresolvers.le.acme.dnschallenge=true"
- "--certificatesresolvers.le.acme.dnschallenge.provider=cloudflare"
- "--certificatesresolvers.le.acme.email=admin@deinedomain.de"
- "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
environment:
- CF_API_EMAIL=admin@deinedomain.de
- CF_DNS_API_TOKEN=${CF_DNS_API_TOKEN}
ports:
- "80:80"
- "443:443"
volumes:
- ./letsencrypt:/letsencrypt
Jeder neue Container braucht nur noch Labels:
labels:
- "traefik.http.routers.meinservice.rule=Host(`app.deinedomain.de`)"
- "traefik.http.routers.meinservice.tls.certresolver=le"
Fertig. Traefik validiert die Domain per DNS-01, stellt das Zertifikat aus, speichert es in acme.json und erneuert es 30 Tage vor Ablauf automatisch. Das ist der Goldstandard, wenn du Container orchestrierst. Der einzige Wermutstropfen: Traefik ist ein ganzes Stück Software, das du betreiben musst, und das acme.json-Format ist nicht mit anderen Tools kompatibel. Willst du das Zertifikat außerhalb von Traefik verwenden, musst du es per Skript aus der JSON extrahieren.
Die 10 Renewal-Fehler, an denen es bei mir gescheitert ist
Ich habe in den letzten acht Jahren mehr Zertifikats-Renewals debuggt, als mir lieb ist. Hier sind die zehn Fehler, die garantiert irgendwann bei dir auftauchen — sortiert nach der Zeit, die ich mit der Fehlersuche verbracht habe.
1. Firewall blockiert Port 80 (HTTP-01)
Der Klassiker. Du hast HTTP-01 konfiguriert, aber deine Firewall — oder die deines Hosters — blockiert eingehende Verbindungen auf Port 80. certbot gibt einen Timeout. Die Lösung ist entweder Port 80 aufmachen oder auf DNS-01 umsteigen. Bei Hetzner Cloud habe ich neulich 45 Minuten gesucht, bis mir einfiel, dass ich die Firewall-Regel für IPv6-Port-80 vor drei Monaten deaktiviert hatte. Let's Encrypt bevorzugt IPv6, wenn ein AAAA-Record existiert — ein Detail, das in der Doku von keinem der drei Tools prominent genug steht.
2. Abgelaufener oder falsch berechtigter DNS-API-Token
DNS-API-Tokens laufen ab, werden rotiert oder stammen aus einer Testumgebung, die du längst gelöscht hast. Die Fehlermeldung ist meist ein kryptisches 403 Forbidden oder authentication failed. Ich habe mir angewöhnt, Tokens in einer zentralen .env-Datei zu verwalten und einmal im Quartal per Skript die Gültigkeit zu prüfen. In größeren Teams wandern die Tokens gern auf den Laptop eines Admins, der das Unternehmen verlässt — dann fallen alle Wildcard-Zertifikate gleichzeitig aus.
3. Rate-Limits: 50 Zertifikate pro Domain pro Woche
Let's Encrypt hat ein hartes Limit: 50 ausgestellte Zertifikate pro registrierter Domain pro Woche (Stand September 2026). Wenn du ein CI/CD-System hast, das bei jedem Deployment ein neues Zertifikat anfordert — zum Beispiel, weil du die acme.json nicht persistierst — bist du nach 50 Deployments gesperrt. Bis zum nächsten Montag 0:00 UTC. Das ist mir mit einem staging-Kubernetes-Cluster passiert, der bei jedem terraform apply ein neues Traefik-Zertifikat anforderte. Seitdem läuft staging grundsätzlich gegen das Let's Encrypt Staging Environment.
4. DNS-Propagation zu langsam
Die DNS-01-Challenge setzt voraus, dass dein TXT-Record weltweit auflösbar ist, bevor Let's Encrypt ihn abfragt. Bei manchen Providern dauert die Propagation 60 Sekunden oder länger, besonders wenn du mehrere Nameserver in unterschiedlichen Regionen hast. dehydrated hat standardmäßig kein sleep eingebaut — du musst es in der Hook selbst vorgeben. 30 Sekunden sleep plus ein expliziter Lookup auf ns1.deinprovider.de, bevor du weitermachst, spart dir die Debugging-Session um 3 Uhr nachts.
5. CAA-Records verbieten Let's Encrypt
Ein CAA-DNS-Record sagt der Welt, welche Certificate Authority für deine Domain ausstellen darf. Wenn da 0 issue "digicert.com" steht, wird Let's Encrypt deine Anfrage ablehnen. Ich habe das bei einer Domain geerbt, die vorher von einem PCI-DSS-konformen Dienstleister betrieben wurde. dig CAA deinedomain.de zeigt dir, ob du ein Problem hast. Ändere es auf 0 issue "letsencrypt.org" oder lösche den Record ganz.
6. Symlinks oder falsche Pfade nach certbot-Renewal
certbot erneuert das Zertifikat und aktualisiert die Symlinks. Wenn dein Webserver die Symlinks nicht auflöst — zum Beispiel, weil du den Container mit einem Bind-Mount auf das Symlink-Ziel statt auf das Verzeichnis gestartet hast — serviert er das alte Zertifikat. openssl s_client -connect deinedomain.de:443 -servername deinedomain.de | openssl x509 -noout -dates zeigt dir die tatsächlichen Gültigkeitsdaten. Wenn die nicht zum Dateisystem passen, hast du vermutlich ein Symlink-Problem.
7. Let's-Encrypt-CA-Server nicht erreichbar
Let's Encrypt läuft auf der acme-v02.api.letsencrypt.org. Wenn dein Server die nicht erreicht — ausgehendes HTTPS geblockt, DNS kaputt, Proxy ohne Internetzugang — schlägt das Renewal fehl. Bei mir war es ein Squid-Proxy, der nur HTTP durchließ und HTTPS-Verkehr droppte, ohne es mir zu sagen. curl -v https://acme-v02.api.letsencrypt.org/directory aus der Perspektive des Servers testet die Erreichbarkeit.
8. Falsche Dateiberechtigungen nach Renewal
certbot und dehydrated schreiben die neuen Zertifikatsdateien mit den Rechten des ausführenden Benutzers. Wenn dein Webserver als www-data läuft und das Zertifikat root:root 600 hat, kann er es nicht lesen. Nach jedem Renewal ein chown und chmod 640 in den Hook einbauen. Ja, auch das ist mir passiert — der nginx ist nach einem Renewal um 4:30 Uhr mit Permission denied auf die privkey.pem gestorben. Seitdem hat jede Hook ein explizites chown www-data:www-data /pfad/zu/certs/*.
9. OCSP-Stapling und veraltete Intermediate-Zertifikate
Nach einem Renewal muss der Webserver die Kette aus Server-Zertifikat und Intermediate-CA-Zertifikaten neu laden. Wenn dein nginx oder Apache das Intermediate nicht aktualisiert, kann OCSP-Stapling fehlschlagen — der Server liefert dann ein gültiges Zertifikat, aber die OSCP-Validierung der Clients schlägt fehl. Der Fehler ist subtil, weil Browser das Zertifikat trotzdem akzeptieren (sie holen sich die OCSP-Response selbst), aber Monitoring-Tools und API-Clients brechen ab. certbot renew --force-renewal und dann nginx -t && systemctl reload nginx fixt das.
10. System-Zeit falsch
Die ACME-Protokollvalidierung hängt an korrekten Systemzeiten. Wenn deine Uhr mehr als ein paar Minuten falsch geht — zum Beispiel, weil NTP nach einem Reboot nicht synchronisiert hat — lehnt Let's Encrypt die Anfrage ab. Das passiert gern in VMs, die aus Snapshots wiederhergestellt werden. timedatectl status vor dem ersten Renewal checken.
Monitoring: Bevor dein Chef dich anruft
Zertifikats-Monitoring ist eine der Sachen, die jeder Admin auf dem Zettel hat und trotzdem aufschiebt. Ich habe drei Ebenen, die mich kostenlos warnen:
Ebene 1 — systemd-Timer: systemctl list-timers certbot zeigt dir, wann das letzte Renewal lief und wann das nächste kommt. Das ist die Minimalvariante, die du immer checken kannst.
Ebene 2 — Shell-Skript: Ein Cronjob, der openssl s_client -connect domain:443 -servername domain </dev/null 2>/dev/null | openssl x509 -noout -enddate ausführt und bei weniger als 14 Tagen Restlaufzeit eine E-Mail schickt. Läuft bei mir auf einem Raspberry Pi im Wohnzimmer, der alle 30 Domains zweimal täglich prüft. Seit drei Jahren kein Ausfall mehr.
Ebene 3 — Externes Monitoring: Dienste wie UptimeRobot oder Cert Spotter überwachen Certificate-Transparency-Logs und melden sich, wenn ein Zertifikat ausläuft. Das ist der Sicherheitsnetz-Ansatz: Selbst wenn dein interner Cronjob und der systemd-Timer beide versagen, kriegst du eine externe Benachrichtigung.
Eine Sache, die ich aus Erfahrung empfehle: Teste den Renewal-Prozess einmal im Monat manuell. Nicht nur die Zertifikatsgültigkeit checken, sondern ein certbot renew --dry-run oder dehydrated -c --force ausführen. Ein Dry-Run, der vor drei Monaten funktioniert hat, ist keine Garantie, dass es heute noch läuft. API-Tokens laufen ab, Firewall-Regeln ändern sich, DNS-Provider stellen ihre API um. Ein monatlicher manueller Check hat mich schon dreimal vor bösen Überraschungen bewahrt.
Welches Tool für welchen Einsatzzweck?
Die Entscheidung hängt weniger von den Features ab als von deinem Setup:
- Klassischer VPS mit nginx: certbot. Es ist in deinen Paketquellen, die Doku ist üppig, und das nginx-Plugin schreibt dir die Config automatisch. Der Overhead vom Python-Ökosystem fällt auf einem VPS mit 2 GB RAM nicht ins Gewicht.
- Embedded, Raspberry Pi, Minimal-Container: dehydrated. Kein Python, keine Abhängigkeiten. Ich betreibe es auf einem Pi Zero W mit 512 MB RAM — der certbot würde das Ding in die Knie zwingen.
- Docker-Compose-Stack oder Kubernetes: Traefik. Sobald du mehr als drei Container hast, ist die Zeit für manuelles Zertifikats-Handling verschwendet. Traefik macht es dir zum Commodity.
Für den Betrieb von mehr als einer Handvoll Domains empfiehlt sich ein Mix: Traefik für die Container-Landschaft, dehydrated für Spezialfälle wie Mailserver oder externe Load-Balancer, die kein Container-Ökosystem haben.
Fazit
Let's Encrypt hat TLS demokratisiert. Was vor zehn Jahren ein Kostenfaktor und organisatorischer Albtraum war, ist heute eine Selbstverständlichkeit — vorausgesetzt, du hast die Automation sauber aufgesetzt. Die drei Wege, die ich hier gezeigt habe, decken vom Ein-Server-Betrieb bis zum Multi-Container-Cluster alles ab. Der größte Feind ist nicht die Technik, sondern die Vernachlässigung: Ein Renewal-Setup, das heute funktioniert, kann in drei Monaten still und leise kaputtgehen. Meine Lehre aus acht Jahren Zertifikats-Automation: Einmal im Monat manuell testen, API-Tokens dokumentieren und das Monitoring auf zwei unabhängige Ebenen verteilen. Dann schläfst du auch an Heiligabend ruhig.