Hält die A-Records der dynamischen Zone (dyn.example) auf der aktuellen öffentlichen IPv4 des Heimanschlusses. Läuft als Container auf der NAS, aktualisiert per RFC 2136 (nsupdate) mit TSIG direkt gegen die BIND-Instanz auf dem Hetzner-VPS. Der Quellcode liegt in Forgejo, Build und Deploy laufen über Komodo (Push → Image in der Forgejo-Registry → Deployment).
Die Seite hält fest, wie er gebaut ist, wie man ihn betreibt und was beim Ausfall im Herbst 2026 schiefging.
Ablauf
alle 5 min
│
▼
öffentliche IPv4 holen ──(Dienst 1 → 2 → 3, je validiert)──► keine gültige Antwort? → Fehler, Healthcheck altert
│
▼
pro Record: aktuelle A-Records beim Nameserver abfragen (dig @ns.example)
│
├─ genau ein Record, gleich der WAN-IP ──► nichts tun
│
└─ abweichend / fehlt / doppelt ──► nsupdate (delete + add) ──► erneut abfragen und gegenprüfen
Wichtig: Der Ist-Zustand kommt vom Nameserver, nicht aus einer Statusdatei im Container. Ein neu erzeugter Container ist damit unkritisch, und ein zwischenzeitlich ausgefallener BIND wird beim nächsten Lauf automatisch nachgezogen.
Betrieb
- Komodo-Deployment (Einzelcontainer), Restart-Policy
unless-stopped, Healthcheck im Image (unhealthy, wenn der letzte erfolgreiche Lauf älter als drei Intervalle ist). - Konfiguration nur über das Environment-Feld des Deployments (
KEY=VALUEpro Zeile). Das Script liest ausschließlich die nummerierten VariablenDDNS_<n>_HOSTundDDNS_<n>_SECRET; wie die Secrets in Komodo heißen, ist egal. Vollständige Variablenliste: README im Repo. - Secrets nur als Komodo-Variablen, nie im Repo.
- Änderung am Environment-Feld → Deploy, nicht nur Restart.
- Logs sind absichtlich still: nur Änderungen und Fehler (
CHANGE,UPDATED,ERROR). MitVERBOSE=1zusätzlich jeder unveränderte Lauf, mitDRY_RUN=1wird nichts gesendet.
TSIG-Keys
- Pro Record ein eigener Key, Key-Name = Record-Name. Die Keys gehören ausschließlich dem Updater.
- Die Zertifikats-Clients (Let’s Encrypt per DNS-Challenge: Home Assistant,
acme.sh, NPM) nutzen einen eigenen, separaten Key. So betrifft eine Rotation immer nur einen Zweck. - Algorithmus
hmac-sha512. Die alten Keys warenhmac-md5(der Standard vonnsupdate, wenn man nichts angibt) – veraltet. - Erzeugen auf dem DNS-Host:
tsig-keygen -a hmac-sha512 <keyname>. - In BIND immer den kompletten Key-Block ersetzen, nicht nur die
secret-Zeile. Bleibt der alte Algorithmus stehen, lehnt der Server mitBADKEYab. - Danach
named-checkconfund den Container neu starten. Ein fehlgeschlagenesrndc reloadlässt BIND stillschweigend mit der alten Konfiguration weiterlaufen.
Update-Pfad testen
Im Normalbetrieb läuft fast immer nur der Zweig „Record stimmt schon“. Der eigentliche Update-Pfad lässt sich gezielt beweisen: einen Record per nsupdate von Hand auf eine Dokumentations-IP (192.0.2.1) setzen und den Updater ihn zurückholen lassen. Erwartet: CHANGE … 192.0.2.1 -> <WAN-IP> und UPDATED …. Der Record steht dabei kurz falsch (TTL 600) – für einen ruhigen Moment einplanen. Den Test für jeden Key machen: Ein Tippfehler im Key-Namen fällt sonst erst beim nächsten echten IP-Wechsel auf.
🔧 Lessons Learned: Ausfall der dynamischen Zone
Datum: September/Oktober 2026
Symptom: AdGuard-UI und NPM-UI über ihre *.dyn-Namen nicht erreichbar, Komodo zeigt die NAS als Unknown, und <nas>.fritz.box löst nicht mehr auf. Die NAS selbst war per IP und DSM erreichbar, alle Container liefen.
Debugging-Weg (in dieser Reihenfolge):
- Ping auf die IP, DSM-UI, Container Manager → NAS gesund. Das Problem liegt bei Namen, nicht beim Host.
diggegen den Heim-Resolver: anderefritz.box-Namen gehen, derdyn-Name liefert SERVFAIL, normale Domains gehen.dig SOA <zone> @1.1.1.1: ebenfalls SERVFAIL, mit den erweiterten Fehlercodes 22/23 („No Reachable Authority“, „at delegation“). Damit ist klar: die ganze Zone ist weltweit tot, nicht nur beim Heim-Resolver. Das Lokale war nur ein Symptom (eindyn-Name der NAS löste kurz weiter, weil AdGuard ihn noch im Cache hatte).- Auf dem VPS fehlte der BIND-Container in
docker ps. In Komodo stand er auf Exited, ebenso zwei weitere Deployments.
Ursache: Die Deployments hatten keine Restart-Policy. Nach einem Neustart des Hosts kam der BIND-Container nicht wieder hoch, und alles unterhalb der dynamischen Zone (Web-UIs über deren Namen, Komodo → NAS, DNS-Challenges) ging mit ihm aus.
Nebenbefund 1 – .fritz.box-Name tot: Die FritzBox führte vier Einträge mit demselben Hostnamen (drei inaktive Karteileichen mit derselben IP und anderen MACs aus früheren Macvlan-Versuchen). Doppelte Hostnamen bringen den FritzBox-eigenen DNS durcheinander. Nach dem Löschen der inaktiven Einträge löste der Name wieder auf.
Nebenbefund 2 – der alte Updater war schon länger kaputt: Er war ein Perl-Script (mit Cron im Container), das die IP per HTML-Scraping bei einem privaten Hobbydienst ermittelte, und ein zweites Script, das eine Datei las, die nur das erste hinterließ. Beide Dienste setzten die Verbindung zurück. Außerdem standen die TSIG-Secrets im Klartext im Repo (und damit in der Git-Historie). Aufgefallen war es nur nicht, weil die WAN-IP wochenlang stabil blieb.
Fix:
- Restart-Policy
unless-stoppedan allen betroffenen Deployments. - Updater komplett neu gebaut: ein kleines POSIX-Shell-Script (
curl,dig,nsupdate), mehrere IP-Dienste mit Fallback und Validierung, Ist-Vergleich beim Nameserver, Gegenprüfung nach dem Update, Eingaben gegen Einschleusen von Zeilen abgesichert, Healthcheck. Getestet mit Mocks für alle Zweige. - Beide Updater-Keys rotiert, auf
hmac-sha512. Die alten Secrets sind damit wertlos, die Git-Historie musste nicht umgeschrieben werden.
Merke:
- Nicht der Host, sondern ein Name. Ist die IP erreichbar, aber Namen sterben, zuerst
diggegen den eigenen Resolver und gegen@1.1.1.1vergleichen. Das trennt „lokaler Cache“ von „Zone tot“ in einer Minute. - Cache-Schichten: AdGuard hält Fehlerantworten und alte Treffer. Nach einer Reparatur den AdGuard-Cache leeren, sonst bleibt man auf der Negativantwort sitzen.
- Komodo-Deployments haben keine Restart-Policy per Default. Bei allem, was die Infrastruktur trägt (DNS!), von Anfang an
unless-stoppedsetzen. - Ein Deployment kennt nur sein Environment-Feld. Eine Variable, die dort nicht steht, existiert im Container nicht. Prüfen mit
env | grep DDNSim Container. - Hostnamen in der FritzBox sind eindeutig zu halten. Alte Geräte mit gleichem Namen (Macvlan-Experimente) aus der Geräteliste entfernen.
- Wenn eine Dyn-DNS-Kette „funktioniert“, weil sich nichts ändert, ist sie nicht getestet. Den Änderungspfad einmal absichtlich provozieren.
Fehlersuche
| Meldung / Symptom | Ursache |
|---|---|
no records configured | DDNS_1_HOST kommt nicht im Container an (Environment-Feld, env | grep DDNS prüfen) |
DDNS_<n>_SECRET is missing or not base64 | Variable fehlt, ist leer oder falsch benannt |
could not determine public IPv4 | alle IP-Dienste nicht erreichbar oder liefern Unsinn |
query to ns… failed | Nameserver nicht erreichbar: läuft der BIND-Container, Port 53 (UDP und TCP) offen? |
NOTAUTH(BADKEY) | BIND kennt Key-Name oder Algorithmus nicht: Tippfehler im Namen, alter hmac-md5-Block, Konfiguration nicht neu geladen |
tsig verify failure / BADSIG | Name und Algorithmus stimmen, das Secret nicht |
update accepted but … still serves … | Update nicht übernommen: Zonenjournal, update-policy prüfen |
Offen / Ideen
- IPv6 (AAAA): bewusst nicht enthalten, solange IPv6 im Heimnetz abgeschaltet ist. Für einen WireGuard-Endpunkt über IPv6 zählt die Adresse der FritzBox selbst, nicht die der NAS – die Adressquelle wäre dann die FritzBox. Erst angehen, wenn Dual-Stack wieder aktiv ist.
- Engere
update-policy: der gemeinsam genutzte ACME-Key sollte nur TXT-Records unter_acme-challengeändern dürfen, die Updater-Keys nur ihren eigenen A-Record. Noch zu prüfen, was aktuell erlaubt ist. - Externer Check auf die SOA der dynamischen Zone (nicht über AdGuard), damit ein Ausfall wie dieser gemeldet wird, bevor man ihn beim Arbeiten bemerkt.
Siehe auch
- Infrastruktur – Netzwerk, DNS und CI/CD insgesamt
- Komodo – Deployments, Builds und ihre Eigenheiten
- AdGuard-Home – der Resolver, der Fehlerantworten cached
- Docker-Macvlan – Ursprung der alten Geräteeinträge in der FritzBox