In jedem Homelab gibt es diesen einen Dienst, bei dem alle gelernt haben, auf „Risiko akzeptieren und fortfahren“ zu klicken. Genau diese antrainierte Klickerei lieben Angreifer. Die gute Nachricht: Deine internen Dienste bekommen echte Zertifikate, ohne dass du einen einzigen Port öffnest.
Kurzantwort
Wie bekommen interne Dienste im Homelab ein vertrauenswürdiges Zertifikat, ohne offenen Port ins Internet? Du nutzt eine eigene Domain und lässt einen ACME-Client die Kontrolle darüber mit der DNS-01-Challenge nachweisen: Der Client schreibt einen TXT-Eintrag unter _acme-challenge, Let’s Encrypt prüft ihn im öffentlichen DNS und stellt dann das Zertifikat aus [1][2]. Mit DNS-01 bekommst du auch ein Wildcard-Zertifikat wie *.home.example.com, das dein Reverse Proxy ausliefert, während die Namen nur in deinem Netz auf private Adressen zeigen. Der Preis: Du brauchst einen DNS-Anbieter mit API, das API-Token wird zu einem wertvollen Geheimnis, und jeder Name in einem Zertifikat landet in öffentlichen Protokollen. Alle Angaben gelten mit Stand 25.10.2026. „Homelab“ meint hier die Server und Dienste, die du zu Hause für dich selbst betreibst.
So funktioniert’s
- Warum
.localund.internalnicht gehen. Öffentliche Zertifizierungsstellen stellen nur für Namen im öffentlichen DNS aus, die du kontrollierst. Die Caddy-Doku zähltlocalhost,.local,.internalund.home.arpazu den Namen, die kein öffentlich vertrauenswürdiges Zertifikat bekommen [11]. Du brauchst also eine echte Domain. Eine Subdomain wiehome.example.comreicht und trennt die Homelab-Namen sauber von deiner Website. - Die Challenge Schritt für Schritt. Das ACME-Protokoll ist in RFC 8555 standardisiert [2]. Dein Client legt einen Kontoschlüssel an und bestellt ein Zertifikat. Für jeden Namen gibt die Zertifizierungsstelle ein Token aus. Der Client verbindet das Token mit dem Fingerabdruck seines Kontoschlüssels (die „Key Authorization“), bildet daraus einen SHA-256-Hash, kodiert ihn in base64url und veröffentlicht das Ergebnis als TXT-Eintrag unter
_acme-challenge.<name>[2]. Let’s Encrypt fragt diesen Eintrag im DNS ab. Stimmt der Wert, gilt der Name als geprüft [1]. Nichts muss dein Netz erreichen, denn es antworten nur die Server deines DNS-Anbieters. - Warum DNS-01 und nicht HTTP-01. HTTP-01 braucht Port 80 aus dem Internet erreichbar, und weder HTTP-01 noch TLS-ALPN-01 können Wildcard-Zertifikate ausstellen [1]. DNS-01 kann beides: Wildcards und Namen, deren Server nicht im öffentlichen Internet stehen [1]. IP-Adressen kann DNS-01 allerdings nicht prüfen [1].
- Was ein Wildcard abdeckt. Ein Wildcard darf nur als komplettes Label ganz links stehen und passt auf genau ein Label [3].
*.home.example.comdeckt alsonas.home.example.comab, aber wederhome.example.comselbst nocha.b.home.example.com[3]. Willst du den Namen ohne Präfix auch, nimm beide in die Bestellung auf, wie es die Beispiele von acme.sh und lego mit-d example.com -d '*.example.com'tun [16][18]. Beide Namen brauchen dann je einen eigenen TXT-Wert unter demselben_acme-challenge-Namen. Mehrere TXT-Einträge sind dort erlaubt, räum alte aber weg, denn zu große Antworten lehnt Let’s Encrypt ab [1]. - Halte das API-Token klein. Let’s Encrypt warnt, dass vollständige DNS-Zugangsdaten auf einem Webserver den Schaden vergrößern, wenn dieser Server gehackt wird. Empfohlen sind eng begrenzte Zugangsdaten oder eine Prüfung von einem separaten Rechner, der die Zertifikate danach verteilt [1]. In der Praxis heißt das: Erstelle ein Token, das nur DNS-Einträge ändern darf, nur in dieser einen Zone, und schalte einen IP-Filter dazu, wenn dein Anbieter einen anbietet. Die acme.sh-Doku nennt am Beispiel Cloudflare genau dieses Minimum: „Zone > DNS > Edit“ für die konkrete Zone, und sie rät vom globalen API-Schlüssel ab [18]. Halte das Token aus Konfigurationsdateien heraus, die in Git landen. Caddy liest es aus einer Umgebungsvariablen, lego aus einer env-Datei, und acme.sh speichert es in seiner
account.conf, also schütze diese Datei [12][16][18]. - Delegiere die Challenge, wenn dein Anbieter schwach ist. Let’s Encrypt folgt beim Abfragen des TXT-Eintrags CNAME- und NS-Einträgen. Du kannst
_acme-challenge.home.example.comdeshalb auf eine eigene Prüfzone bei einem Anbieter mit guter API zeigen lassen [1]. Deine Hauptzone braucht dann überhaupt kein API-Token. Caddy hat dafürdns_challenge_override_domain; Traefik nennt CNAME-Unterstützung „supported and even encouraged“ [12][15]. - Plane die Verteilzeit ein. DNS-Anbieter brauchen Zeit, bis ein neuer Eintrag auf allen ihren Servern sichtbar ist. Ohne API, die das meldet, rät Let’s Encrypt, lange genug zu warten, oft bis zu einer Stunde [1]. Clients prüfen den Eintrag, bevor sie die Validierung anstoßen. Caddy wartet standardmäßig bis zu 2 Minuten (
propagation_timeout), Traefik hatpropagation.delayBeforeChecks, lego--dns.propagation.wait[12][15][17]. Eine typische Homelab-Falle: Beantwortet dein interner Resolver deine eigene Zone, sieht die Prüfung womöglich die interne Antwort statt der öffentlichen. Lenk die Prüfung mit Caddysresolversoder TraefiksdnsChallenge.resolversauf öffentliche Resolver [12][15]. Das ist unsere Lesart dieser Optionen, keine Aussage der Projekte. - Wähle einen Client. Caddy braucht einen Build, der das Modul für deinen DNS-Anbieter aus den
caddy-dns-Repositories enthält. Den lädst du auf der Caddy-Seite herunter oder baust ihn mitxcaddy[12][14]. Traefik nutzt intern lego und listet dessen DNS-Anbieter mit den jeweils nötigen Umgebungsvariablen [15]. acme.sh ist ein Shell-Skript mit eigenen DNS-API-Skripten, lego ein einzelnes Go-Programm [16][18]. Unsere Einschätzung: Wenn du einen dieser Proxys schon betreibst, nimm dessen eingebaute ACME-Unterstützung. Ein eigener Client lohnt sich, wenn auch andere Geräte das Zertifikat brauchen. - Löse die Namen lokal auf. Die öffentliche Zone braucht nur die
_acme-challenge-Einträge. Dein interner Resolver (Router, Pi-hole, AdGuard Home oder Unbound) beantwortet*.home.example.commit der privaten Adresse deines Reverse Proxys. So stehen interne Adressen nicht im öffentlichen DNS. Unsere Einschätzung: Diese Trennung ist die sauberere Wahl. Trägst du private Adressen stattdessen öffentlich ein, kommt dir der Schutz vor DNS-Rebinding in die Quere. Unbound entfernt private Adressen aus Antworten für öffentliche Namen, wenn duprivate-addresssetzt, und erlaubt einzelne Domains mitprivate-domain. Einträge, die du lokal mitlocal-dataanlegst, sind standardmäßig erlaubt [19]. OPNsense bietet dasselbe als „Private Domains“ und „Rebind Protection networks“ an [20]. Eine FRITZ!Box unterdrückt solche Antworten ebenfalls. Ausnahmen trägst du als vollständige Hostnamen unter Heimnetz > Netzwerk > Netzwerkeinstellungen im Bereich „DNS-Rebind-Schutz“ ein; je nach FRITZ!OS-Version liegt er hinter den erweiterten Einstellungen [21][22]. - Laufzeiten und Erneuerung. Standardzertifikate von Let’s Encrypt gelten 90 Tage. Kurzlebige Zertifikate mit rund 6 Tagen sind optional, und das Profil
tlsserverstellt seit dem 13. Mai 2026 Zertifikate mit 45 Tagen aus [5][6][7]. Am 10. Feb. 2027 sinkt der Standard auf 64 Tage, am 16. Feb. 2028 auf 45 Tage. Der Zeitraum, in dem eine erfolgreiche Prüfung wiederverwendet werden darf, schrumpft von 30 Tagen auf 10 Tage und dann auf 7 Stunden [7][8]. Let’s Encrypt empfiehlt ACME Renewal Information (ARI), die dem Client sagt, wann er erneuern soll. Eine feste Erneuerung nach 60 Tagen reicht künftig nicht mehr [7]. lego nutzt ARI, solange du es nicht abschaltest [17]. Traefik plant Erneuerungen mitcertificatesDurationund nimmt dafür 2.160 Stunden (90 Tage) an [15]. Unsere Lesart: Wählst du dort ein kürzeres Profil, passe diesen Wert an. Let’s Encrypt hat die Ablauf-E-Mails am 4. Juni 2025 eingestellt [8], überwache die Ablaufdaten also selbst. - Rate Limits und Staging. Mit Stand 25.10.2026 erlaubt Let’s Encrypt 50 Zertifikate pro registrierter Domain und 5 pro identischer Namensliste innerhalb von 7 Tagen, dazu 5 fehlgeschlagene Prüfungen pro Name und Konto pro Stunde [4]. Erneuerungen über ARI sind von allen Grenzen ausgenommen. Eine Erneuerung ohne ARI (gleiche Namensliste) zählt nicht zur Grenze pro Domain, wohl aber zu den 5 pro Namensliste und zu den fehlgeschlagenen Prüfungen [4][9]. Teste mit der Staging-Umgebung, die deutlich höhere Grenzen hat [4]. Caddy (
ca), Traefik (caServer) und lego (--server) lassen dich den Endpunkt umstellen [12][15][17]. - CAA als Schloss an der Tür. Ein CAA-Eintrag nennt die Zertifizierungsstellen, die für deine Domain ausstellen dürfen.
issue "letsencrypt.org"erlaubt Let’s Encrypt; der Parametervalidationmethods=dns-01beschränkt auf DNS-Prüfung,accounturiauf dein ACME-Konto [10]. Die Zertifizierungsstelle sucht den CAA-Eintrag, der dem Namen am nächsten liegt, und folgt dabei CNAMEs [10]. - Namen werden öffentlich. Ausgestellte Zertifikate erscheinen in öffentlichen Certificate-Transparency-Protokollen, und Let’s Encrypt verweist auf Suchwerkzeuge wie crt.sh [4]. Ein Zertifikat für
nas.home.example.comverrät also, dass es diesen Namen gibt. Unsere Lesart: Ein Wildcard hält die einzelnen Dienstnamen aus dem Zertifikat heraus. - Die Alternative: eine eigene CA. Ohne Domain oder in einem Netz, das offline bleiben muss, kann Caddy eine eigene lokale Zertifizierungsstelle betreiben. Die braucht weder ACME noch DNS, aber jedes Gerät muss ihrer Root vertrauen [11]. Unser Reverse-Proxy-Guide vergleicht beide Wege genauer.
Praxisbeispiel (example.com und die Adressen sind Platzhalter)
Der Aufbau: ein Reverse Proxy auf einer privaten Adresse, eine Domain example.com, Homelab-Namen unter home.example.com. Kein Port wird weitergeleitet.
| Wo | Eintrag | Zweck |
|---|---|---|
| Öffentliches DNS | example.com CAA 0 issue "letsencrypt.org;validationmethods=dns-01" | Nur Let’s Encrypt, nur per DNS-01 [10] |
| Öffentliches DNS | _acme-challenge.home.example.com TXT ... | Setzt und löscht der ACME-Client |
| Interner Resolver | *.home.example.com → 192.168.1.10 | Namen zeigen auf den Proxy, von außen unsichtbar |
Das Caddyfile folgt dem Wildcard-Muster aus der Caddy-Doku. Cloudflare ist dort der Beispielanbieter, keine Empfehlung von uns, und jedes Anbieter-Modul hat seine eigene Syntax [12][13]:
*.home.example.com {
tls {
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
resolvers 1.1.1.1
}
@nas host nas.home.example.com
handle @nas {
reverse_proxy 192.168.1.20:5000
}
handle {
abort
}
}abort schließt Verbindungen für Namen, die du nicht eingerichtet hast [13]. Das Token steht in einer Umgebungsvariablen, nicht in der Datei. Für den ersten Lauf stellst du den Staging-Endpunkt ein und wechselst zur Produktion, sobald alles klappt [12]. Mit lego sieht dieselbe Bestellung mit dem Token in einer eigenen env-Datei so aus, wieder mit dem Anbieter aus der lego-Doku. Seit lego v5 stehen alle Flags hinter run [16][17]:
lego run --server https://acme-staging-v02.api.letsencrypt.org/directory --dns cloudflare --domains 'home.example.com' --domains '*.home.example.com' --env-file .env.dnsWir haben diese Konfigurationen nicht gegen einen echten Anbieter laufen lassen. Sie folgen der Dokumentation, und Optionen können sich zwischen Versionen ändern.
Prüfliste
- Wähle eine Subdomain einer eigenen Domain für alle Homelab-Namen.
- Prüfe, ob dein DNS-Anbieter eine API hat und dein Client sie unterstützt, oder delegiere
_acme-challengeper CNAME. - Erstelle ein Token, das nur DNS-Einträge in dieser Zone ändern darf, und speichere es außerhalb von Git.
- Teste mit der Staging-Umgebung von Let’s Encrypt, bevor du auf Produktion umstellst.
- Bestelle das Wildcard und bei Bedarf den Namen ohne Präfix in derselben Bestellung.
- Lege lokale DNS-Einträge für die Namen an, und erlaube die Domain im Rebind-Schutz nur, wenn du private Adressen öffentlich einträgst.
- Setze einen CAA-Eintrag mit
letsencrypt.orgundvalidationmethods=dns-01. - Schalte ARI oder einen Erneuerungsplan ein, der mit 45-Tage-Zertifikaten klarkommt, und überwache die Ablaufdaten.
- Denk daran, dass jeder Name in einem Zertifikat öffentlich ist, und halte interne Details aus den Namen heraus.
Passt dazu: Reverse Proxy fürs Homelab; DNS; Zero Trust; DNS-Werbeblocker: Pi-hole oder AdGuard Home; DDNS; WireGuard oder Tailscale fürs Homelab; Uptime Kuma.