← alle Posts

20. August 2026 Homelab

Jetzt wird’s redundant: Secondary DNS im Homelab

Wie mein zweiter DNS-Server erst mal genau das kaputtgemacht hat, wofür ich ihn eigentlich gebaut habe.

Die Lücke aus dem letzten Post

In meinem Beitrag über den Umstieg auf Technitium steht unter „Die Schattenseiten“ ein Absatz, der mich gewurmt hat, seitdem ich ihn geschrieben habe:

Der Klassiker = SPoF (Single Point of Failure). Wenn DNS steht, steht alles, und zwar auf eine besonders verwirrende Art (nichts ist „kaputt“, es geht nur nichts mehr). […] Bei mir läuft trotzdem aktuell nur eine Instanz. Das ist eine Lücke in meinem Setup, und sie steht auf der ToDo-Liste.

Diese Lücke ist jetzt geschlossen. Inzwischen läuft ein weiterer Technitium auf einem zweiten Host, die Zonen replizieren und ein Wartungsfenster für den ersten Server legt damit nicht mehr das halbe Homelab auf Eis.

Auf dem Weg dahin bin ich allerdings in eine Falle getappt, und die ist der eigentliche Grund für diesen Beitrag: Der zweite DNS-Server, den ich gebaut habe, um meinen Split-Horizon abzusichern, hat ihn für ein paar Minuten genau unterlaufen. Ohne Fehlermeldung, ohne Warnung, mit voller Autorität.

Warum ausgerechnet jetzt

Der Anlass für den (Um-)Bau war ein konkreter Termin. Auf dem Host, auf dem mein DNS läuft, liegen auch Reverse Proxy, Git-Server und Monitoring. Dieser Container stand vor einem Betriebssystem-Rebuild, weil die Ubuntu-Version ihr Lebensende erreicht hat. Von den Containern, die dafür umgebaut werden mussten, waren alle bis auf zwei fertig, und einer der verbliebenen war ausgerechnet der mit dem DNS drin.

Ohne zweiten DNS heißt „Container neu bauen“: Das ganze Haus ist für die Dauer der Aktion blind. Nicht nur externes Surfen, sondern auch sämtliche internen Namen, weil der Split-Horizon dort hängt. Mit zweitem DNS schrumpft das auf „ein paar Dienste sind kurz weg“.

Was als Fallback ausdrücklich nicht taugt: einfach 1.1.1.1 als zweiten DNS-Eintrag ins DHCP zu schreiben. Der kennt meine internen Zonen nicht. Fragt ein Gerät ihn nach einem internen Namen, bekommt es die öffentliche Antwort zurück, und der Split-Horizon ist ausgehebelt. Darauf komme ich gleich noch mal zurück.

Der Secondary läuft deshalb auf einem zweiten physischen Host, nicht als zweiter Container auf demselben. Nur so hilft er gegen einen Hostausfall. Einen externen vServer habe ich erwogen und verworfen: Meine internen Zonen enthalten ausschließlich private Adressen, der Zonentransfer müsste durch einen Tunnel, und die komplette interne Namensstruktur läge außerhalb des eigenen Netzes. Für vier Zonen ist mir das zu viel Angriffsfläche.

Wie zwei DNS-Server sich einigen

Wer bisher nur einen Resolver betrieben hat, braucht ein paar Begriffe, die vorher nie eine Rolle gespielt haben. Der Reihe nach:

Primary und Secondary Zone. Der Primary hält die Zone und ist die Quelle der Wahrheit. Der Secondary hält dieselbe Zone als Kopie, die er sich beim Primary abholt. Beide antworten autoritativ, ein Client merkt keinen Unterschied. Wichtig fürs Verständnis: Der Secondary ist kein Cache und kein Proxy. Er hat die vollständigen Daten und funktioniert auch dann, wenn der Primary weg ist.

Zonentransfer (AXFR/IXFR). So kommt die Kopie zustande. AXFR überträgt die komplette Zone, IXFR nur die Änderungen seit einer bestimmten Version. Das läuft über TCP, nicht über UDP.

SOA-Serial. Jede Zone hat eine Seriennummer im SOA-Record. Der Primary erhöht sie bei jeder Änderung, der Secondary vergleicht: höhere Nummer drüben, also brauche ich ein Update. Praktisch für den Hausgebrauch: Mit dig SOA <zone> @<server> kann man beide Server fragen und die Nummern nebeneinanderlegen. Sind sie gleich, ist die Replikation aktuell. Das wird gleich noch wichtig.

NOTIFY. Ohne NOTIFY fragt der Secondary erst nach Ablauf seines Refresh-Intervalls nach, je nach Zone also Minuten bis Stunden. Mit NOTIFY schickt der Primary bei jeder Änderung aktiv ein Signal und der Secondary holt sofort. Bei mir stand ein neu angelegter Record nach 12 Sekunden auf dem Secondary.

TSIG. Ein gemeinsames Geheimnis plus Algorithmus (hier HMAC-SHA256), mit dem die Anfragen signiert werden. Damit weiß der Primary: Diese AXFR-Anfrage kommt wirklich von meinem Secondary und nicht von jemandem, der sich dafür ausgibt. Ohne das könnte im Prinzip jeder im Netz die komplette interne Zone abziehen, also die vollständige Liste aller Geräte- und Dienstnamen samt Adressen. Zusätzlich habe ich den Transfer per IP-ACL auf die Adresse des Secondary beschränkt. Etwas doppelt gemoppelt, aber bei einem Dienst, an dem alles hängt, finde ich das angemessen.

Der Aufbau

Der eigentliche Aufbau ist relativ unspektakulär:

  1. Zweiter Container auf dem zweiten Host. Zwei Cores, zwei GB RAM, zehn GB Platte, und das ist eher großzügig als knapp bemessen. Technitium läuft als Docker-Container im Host-Netzwerkmodus, damit es Port 53 direkt bekommt.
  2. Stolperstein Port 53. Ubuntu bringt systemd-resolved mit einem Stub-Listener auf 127.0.0.53:53 mit, und solange der läuft, bekommt Technitium den Port nicht. Ein DNSStubListener=no in einer Drop-in-Datei unter /etc/systemd/resolved.conf.d/, danach systemctl restart systemd-resolved, und die Sache ist erledigt. Das trifft für jeden selbstgehosteten DNS auf Ubuntu zu.
  3. Gleiche Version auf beiden Instanzen. Klingt banal, aber: Wer seine Container automatisiert aktualisieren lässt, muss Primary und Secondary gemeinsam anheben, nie einzeln.
  4. TSIG-Key auf beiden Servern anlegen, gleicher Name, gleiches Secret. Das geht nur über die Weboberfläche, denn die API kann TSIG-Keys nur zuweisen, nicht erzeugen. Wer sein Homelab gerne via Code konfiguriert, sollte das wissen.
  5. Am Primary pro Zone: Zonentransfer erlauben, auf die IP des Secondary beschränken, TSIG-Key zuweisen, NOTIFY auf den Secondary richten.
  6. Am Secondary: die Zonen als Secondary Zone anlegen, Primary-Adresse und TSIG-Key eintragen.
  7. Erst danach DHCP. Den neuen Server als zweiten DNS-Eintrag ausrollen, und zwar für IPv4 und IPv6. Letzteres wird gern vergessen, wenn das Netz Dual-Stack fährt. *hust*

Punkt 7 steht bewusst am Ende. Das war die einzige wirklich kluge Entscheidung an diesem Abend.

Die Falle

Ich habe die Secondary Zones über die API angelegt, inklusive Parameter für den TSIG-Key. Die API antwortete mit ok. Die Zonen erschienen in der Übersicht. Alles sah richtig aus.

Aber: Der Parameter wurde stillschweigend ignoriert. Das Feld blieb leer. Keine Warnung, kein Fehler, kein Hinweis.

Der Secondary fragte den Zonentransfer daraufhin unsigniert an. Der Primary verlangte eine TSIG-Signatur und lehnte ab, RCODE=Refused, sichtbar ausschließlich im Logfile des Secondary. In der Zonenübersicht stand weiterhin alles korrekt da.

Und jetzt der Teil, der mir den Abend gerettet und gleichzeitig verdorben hat:

Ein Secondary mit leerer Zone antwortet nicht mit einem Fehler.

Er fällt auf seine rekursive Auflösung zurück und beantwortet die Namen aus dem öffentlichen DNS. Konkret gemessen:

Ausgerechnet der Server, den ich zur Absicherung des Split-Horizon aufgestellt habe, hat ihn ausgehebelt. Nur diesmal nicht durch einen fremden Resolver, sondern durch meinen eigenen zweiten Server. 🙈

Für die Clients wäre das unsichtbar geblieben. Keine Fehlermeldung, kein Timeout, nur „die Seite lädt manchmal nicht“, je nachdem, welchen der beiden Resolver ein Gerät gerade erwischt. Das ist die Sorte Fehler, die man drei Wochen später sucht (und dann beim falschen Dienst).

Der Fix war eine Minute Arbeit: die Zonenoptionen nach dem Anlegen noch einmal separat setzen, dort greift der Parameter, dann einen Resync auslösen. Danach waren alle vier Zonen sofort synchron.

Aufgefallen ist es nur, weil ich vor dem DHCP-Schritt geprüft habe, ob der neue Server die richtigen Antworten gibt. Nicht ob er antwortet. Ob er richtig antwortet.

Zwischen „angelegt“ und „an die Clients ausgerollt“ gehört zwingend eine Prüfung: SOA-Serials beider Server vergleichen und ein paar interne Namen gegen den neuen Server auflösen. Erst wenn beides stimmt, darf er ins DHCP.

Was der Zonentransfer nicht mitnimmt

Ein Zonentransfer repliziert Zonendaten. Sonst nichts. Alles, was unter „Settings“ liegt, läuft pro Server:

Im Alltag heißt das: Jede Blocking-Änderung muss auf beiden Servern gemacht werden. Wer das vergisst, bekommt wieder dieses „geht mal, geht mal nicht“, je nachdem, welchen Resolver das Gerät gerade fragt. Bei mir ist genau das beim Aufbau aufgefallen: Der Primary hatte eine Blockliste, der neue Secondary keine. Insofern: Nachgezogen und mit einer bekannten Werbedomain gegengeprüft.

Damit verbunden eine Frage, die sich im Alltag mehr als einmal gestellt hat (hauptsächlich von der Freundin):

Wie komme ich schnell auf eine geblockte Seite?

Technitium bietet drei Wege, und alle drei muss man auf beiden Servern gehen:

  1. Einzelne Domain dauerhaft freigeben. Im Query-Log auf „Allow“ klicken, die Domain landet in den Allowed Zones.
  2. Blocking temporär abschalten. Settings → Blocking → Temporarily Disable Blocking, Anzahl Minuten eintragen, schaltet sich selbst wieder ein. Und, meine favorisierte Lösung:
  3. Per Knopfdruck vom Handy. Es gibt keine Technitium-App, aber der Endpunkt dahinter ist ein simpler HTTP-GET. Ein iOS-Kurzbefehl auf dem Homescreen macht damit genau das, was die AdGuard-Home-App konnte: zwei URLs, eine pro Server, in einen Kurzbefehl. Ein zweiter Kurzbefehl mit minutes=0 schaltet sofort wieder scharf. Getestet, funktioniert – und das erstaunlich gut.

Technitium hat inzwischen auch ein „Clustering“-Feature, das genau diese Konfiguration über mehrere Instanzen synchronisiert, also das, was der Zonentransfer nicht mitnimmt. Das ist die naheliegende Antwort auf diesen ganzen Abschnitt.

Darauf hat mich im Fediverse jemand hingewiesen, der Technitium als Cluster mit zwei Nodes betreibt und damit einen Node problemlos aktualisieren kann. Meine Antwort damals: „Das mach ich hier auf jeden Fall auch noch.“ Gebaut habe ich es bis heute nicht, und für diesen Beitrag habe ich es auch nicht getestet. Muss ich mir noch mal in Ruhe anschauen.

Sein Argument war dabei ein anderes als meins hier oben, und im Rückblick das bessere: Ihm geht es nicht um die doppelte Blocklisten-Pflege, sondern darum, einen Node aktualisieren zu können, ohne dass jemand etwas merkt. Also genau der Fall, für den ich mir den Secondary überhaupt hingestellt habe. Beim Bau habe ich ihn aber schlicht nicht mehr auf dem Schirm gehabt. 🤦

Erreichbarkeit ist nicht Korrektheit

Next step: Überwachung des Dienstes. Der naheliegende Monitor prüft: Antwortet der zweite DNS-Server? Das alleine reicht aber nicht.

Der gefährlichere Fall ist der stille Replikationsstopp. Der Secondary läuft, antwortet zügig, wirkt kerngesund und liefert dabei veraltete Daten. Mit voller Autorität, versteht sich. Ein reiner Erreichbarkeits-Monitor bleibt dabei grün, denn erreichbar ist er ja.

Deshalb gibt es einen zweiten Monitor, der die eigentlich relevante Frage stellt: Stimmen die SOA-Serials von Primary und Secondary überein? Umgesetzt als kleines Skript, das für jede Zone beide Server per dig fragt und nur bei vollständiger Übereinstimmung Entwarnung gibt. Alle 15 Minuten per Cron, Ergebnis an die Monitoring-Instanz.

Das ist übertragbar über DNS hinaus, und es ist im Grunde dieselbe Lehre wie oben bei der Falle:

Ein Dienst, der antwortet, ist nicht dasselbe wie ein Dienst, der Recht hat.

Was ein zweiter DNS nicht leistet

Damit kein falscher Eindruck entsteht: Redundanz beim DNS ist träge und liegt außerhalb meiner Kontrolle.

Es gibt keinen Mechanismus, der Clients aktiv umschaltet. Jedes Gerät entscheidet selbst, wann es den zweiten Eintrag probiert. Viele erst nach einem Timeout, manche cachen die Reihenfolge, und einzelne Geräte (gern IoT-Kram und Smart-TVs) hängen bis zum Neustart (!) am ersten Server. Ein zweiter DNS macht einen Ausfall also milder, aber nicht unsichtbar. Für meinen eigentlichen Zweck, die geplante Wartung am Primary, reicht das vollkommen. Für das Ziel „der Ausfall fällt niemandem auf“ reicht es nicht.

Die Lösung dafür wäre eine geteilte virtuelle IP per VRRP, in der Praxis meist keepalived. Die Clients bekommen dann nur eine Adresse, und die wandert zwischen den Servern. Fällt der Master aus, übernimmt das Backup die IP in ein bis drei Sekunden – für Clients unsichtbar, weil dieselbe Adresse weiter antwortet.

Ich habe das bewusst nicht gebaut, obwohl ich ja gerne mit sowas rumspiele:

Es gibt einen klaren Trigger, ab dem ich das anders bewerten würde: Wenn beim nächsten echten Ausfall auffällt, dass bestimmte Geräte gar nicht umschalten. Dann ist es nämlich kein Geschwindigkeitsproblem mehr, sondern ein Funktionsproblem, und dafür wäre dann eine virtuelle IP die richtige Antwort. Vorher aber nicht.

Ähnliches gilt für Catalog Zones, die ich im Ursprungspost erwähnt hatte: Damit muss man neue Zonen nicht auf jedem Server einzeln anlegen. Bei meinen vier Zonen lohnt der Aufwand schlicht nicht.

Fazit

Der Stand nach dem Umbau:

Ein Punkt zum Schluss, der mich selbst kurz irritiert hat: Der neue Container braucht kein Backup. Ein Secondary hält keinen eigenen Zustand, er ist jederzeit aus dem Primary reproduzierbar. Was gesichert gehört, ist nicht der Container, sondern das Wissen, und das sind hier genau zwei Dinge: der Name des TSIG-Keys und sein Secret. Beides liegt im Passwortspeicher, nicht im Backup-Volume.

Learning:

Ein Secondary, der nicht repliziert, ist schlimmer als gar keiner: Er ist ein zweiter Resolver, der falsche Antworten mit voller Autorität gibt. Redundanz ist erst dann Redundanz, wenn man geprüft hat, dass die zweite Instanz dasselbe sagt wie die erste, und nicht nur, dass sie überhaupt antwortet.

Der Beitrag, an den dieser hier anknüpft: Technitium im Homelab: DNS selbst in die Hand nehmen.

DNS Homelab Monitoring Netzwerk Redundanz Self-Hosted Split-Horizon Technitium

Kommentar schreiben

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert. Kommentare erscheinen nach Freischaltung.

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre, wie deine Kommentardaten verarbeitet werden.