PowerDNS Hidden Primary: So läuft DNS in meinem Homelab

PowerDNS Hidden Primary: Architektur-Diagramm mit WireGuard-Tunnel, Slaves und SOA-Check im Homelab
Teilen:

Inhalt

DNS fällt im Incident meist erst auf, wenn schon alles andere rot ist. Mail hängt. Ein interner Service findet seinen Namen nicht mehr. Oder die Delegation zeigt seit Stunden auf alte Daten — und niemand hat es gemerkt, weil es ja noch gestern ging.

Ich mache DNS, Infrastruktur, Plattformen, Netzwerk beruflich — das ist mein Alltag, kein Wochenendprojekt. Genau deshalb hatte ich keine Lust, meine Zonen bei irgendeinem externen Anbieter liegenzulassen, den ich bei Serial-Problemen erst kontaktieren oder mit Tickets beschäftigen müsste. Ich will selbst sehen, was der SOA macht. Ich will wissen, ob das NOTIFY rausging. Und wenn ein PTR falsch sitzt, ändere ich ihn selbst — ohne Ticket und ohne träges Blackbox-Dashboard.

Im Homelab sieht das bei mir so aus

Ein Hidden Primary auf Ubuntu 24.04, zwei externe Slaves für die öffentliche Sicht und dazwischen WireGuard. Der Primary schreibt, die Slaves antworten nach draußen. NOTIFY und Zonentransfer laufen ausschließlich durch den Tunnel — offene AXFR-Ports im Internet wollte ich unter allen Umständen vermeiden.

Homelab (Hidden Primary) wg0 Slave 1 ──► öffentliche NS
LAN + wg0 ─────────► Slave 2 ──► öffentliche NS

Hidden heißt hier ganz pragmatisch: Mein Primary steht in keiner öffentlichen Delegation. Das Internet sieht ausschließlich die beiden Slaves. Ihn versehentlich auf 0.0.0.0:53 zu binden, wäre für mich ein eigener, hausgemachter Incident — plötzlich wäre er autoritativ sichtbar, obwohl er doch nur als reine Schreibquelle dienen soll.

Spätestens hier trennt sich für mich die Theorie vom Betrieb. Auf dem Host trenne ich LAN und wg0 daher strikt. SSH, PowerAdmin und der übliche Admin-Kram laufen über das LAN. Der authoritative DNS- und Master/Slave-Verkehr darf ausschließlich über WireGuard fließen. Die öffentlichen Endpoints der Slaves brauche ich für den reinen Tunnel-Handshake, nicht für AXFR-Transfers über das offene Netz.

Was passiert, wenn sich eine Zone ändert

Wenn ich einen Record ändere — bei mir meistens über   PowerAdmin, manchmal direkt per API, ganz selten mit pdnsutil beim Debuggen —, landet der Eintrag in SQLite. Der SOA-Serial steigt, der Primary schickt ein NOTIFY durch den Tunnel, und die Slaves ziehen sich die Daten per AXFR oder IXFR. Klingt simpel.

Ist es meistens auch — bis zu dem Moment, an dem es das eben nicht mehr ist. Da hat die UI zwar brav gespeichert gemeldet, aber der Serial auf den öffentlichen Nameservern bleibt auf dem alten Stand. Oder der Tunnel steht zwar optisch, aber das NOTIFY kommt am Slave nie an, weil die AllowedIPs auf einer Seite nicht passen. Handshake sieht gut aus, das DNS ist trotzdem stale.

pdnsutil show-zone example.com
dig @<public-ns-slave1> example.com SOA +short
dig @<public-ns-slave2> example.com SOA +short

In solchen Momenten habe ich mir angewöhnt, gar nicht erst am Record selbst herumzudoktern. Wenn die Serials divergieren, debugge ich nicht den Record. Ich debugge Replikation.

SQLite, Config, WireGuard

Als Backend nutze ich gsqlite3 — eine schlichte Datei unter /var/lib/powerdns/pdns.sqlite3. Genau deshalb wollte ich auch kein MariaDB im Homelab betreiben: Ich habe schlicht keine Lust, für eine minimale Schreibrate einen zweiten Dienst zu patchen und zu sichern, den SQLite im Vorbeigehen erledigt. Der Haken an der Sache ist bekannt: SQLite mag absolut keine parallelen Writer. PowerAdmin und pdnsutil gleichzeitig auf dieselbe Zone loszulassen, ist der schnellste Weg, sich selbst zu ärgern.

# /etc/powerdns/pdns.d/gsqlite3.conf
launch=gsqlite3
gsqlite3-database=/var/lib/powerdns/pdns.sqlite3

Hier sind die entscheidenden Zeilen meiner pdns.conf — die Platzhalter müsst ihr euch natürlich durch eure echten WireGuard-Adressen ersetzen:

primary=yes
local-address=<primary-wg-ip>
allow-axfr-ips=<slave1-wg-ip>,<slave2-wg-ip>
allow-notify-from=<slave1-wg-ip>,<slave2-wg-ip>
also-notify=<slave1-wg-ip>,<slave2-wg-ip>
only-notify=<slave1-wg-ip>,<slave2-wg-ip>
api=yes
webserver=yes
webserver-address=0.0.0.0
webserver-port=8081
webserver-allow-from=127.0.0.1,<admin-net>/<prefix>

Dass local-address starr auf der WireGuard-Adresse liegt, is für mich der wichtigste Sicherheitshebel: Port 53 lauscht nur dort, nicht im LAN, nicht auf allem. Eine Bind-Einstellung ist mir im Zweifel immer lieber als eine Firewall-Regel, die man versehentlich überschreibt.

allow-axfr-ips und only-notify zeigen auf genau diese zwei Slaves — mehr nicht. Die API brauche ich primär für PowerAdmin; der API-Key liegt getrennt, geregelt über webserver-allow-from. Strikter wäre es, auch die webserver-address direkt an die WG-IP zu binden und Port 8081 per Firewall dichtzumachen — das steht ehrlich gesagt noch auf meiner Todo-Liste.

WireGuard ist für mich eben kein Komfort-VPN, sondern ein harter Transportkanal. Ohne Tunnel gibt es keinen Transfer, und ohne Transfer veralten die Slaves. Dann antwortet die öffentliche Welt auf Anfragen mit dem Stand von gestern.

Zuvor musste ich unter Ubuntu 24.04 natürlich noch systemd-resolved vom Port 53 vertreiben — das übliche Elend.   PowerDNS selbst kommt bei mir sauber aus dem offiziellen auth-49-Repo über das Paket pdns-server mit pdns-backend-sqlite3. Ein bisschen Standardfrickel, aber nicht der Teil, der mir beim Aufbau Kopfzerbrechen bereitet hat.

Alltag

Records ändere ich im Alltag fast nur über PowerAdmin. Die CLI mit pdnsutil nutze ich eigentlich nur zum schnellen Gegenchecken: list-all-zones, check-zone oder mal ein beherztes increase-serial, wenn ich mir nicht ganz sicher bin, ob die UI den Serial sauber mitgenommen hat.

Seitdem mache ich das grundsätzlich so: Nach jeder Änderung schaue ich mir sofort den SOA auf dem Primary an und vergleiche ihn per dig mit den beiden Slaves. Das klingt furchtbar pedantisch. Ist es auch. Aber es erspart mir genau die Abende, an denen man ratlos vor dem Monitor sitzt und sich fragt, warum die halbe Welt noch alte Records aufgelöst bekommt.

Wenn bei mir mal wieder was hakt, ist meine Reihenfolge beim Troubleshooting absolut festgefahren: Erst wg show, dann journalctl -u pdns, und schließlich der Serial-Vergleich. Fast immer ist es der Tunnel, manchmal schlicht der Serial, und nur in den seltensten Fällen der Record selbst. SQLite-Locks fängt man sich am ehesten ein, wenn man wie wild parallel in der UI klickt und zeitgleich auf der CLI an der Zone schraubt. Und ob der Primary vielleicht doch unbemerkt und unerwünscht auf LAN-Port 53 lauscht, sehe ich mit einem schnellen ss -lntup | grep :53 ohnehin schneller als in jeder noch so gut gepflegten Dokumentation.

Die Backup-Strategie ist einfach: Die SQLite-Datei wird wegkopiert. Entweder stoppe ich dafür kurz pdns oder nutze das Online-.backup. Updates kommen unkompliziert über das PowerDNS-Repo, gefolgt von einem schnellen Restart und dem obligatorischen Serial-Check gegen die Slaves.

Ein paar eiserne Prinzipien habe ich mir über die Jahre angewöhnt: Kein Recursor auf demselben Host. Kein MariaDB ohne Grund. Keine offenen AXFR-Quellen im Netz. Authoritative und Resolver bleiben bei mir strikt getrennte Geschichten.

DNS ist nicht glamourös und wird es auch nie sein. Aber wer seine eigenen Zonen nicht liest, jagt am Ende nur Symptome hinterher. Ich fange deshalb beim Debuggen immer mit dem SOA und dem Tunnel an — und ganz sicher nicht in der Benutzeroberfläche.

Diskurs: Mastodon Bluesky
Quellverweise: PowerDNS Authoritative Docs PowerDNS Master/Slave PowerDNS Repositories PowerDNS API
Tags: #PowerDNS #Ubuntu 24.04 #SQLite #WireGuard #DNS #Homelab #PowerAdmin #Hidden Primary #Master-Slave

Nächster

Zorin OS 18.1: Point-Release für Umsteiger