DHCP und DNS selbst in die Hand nehmen

Nachdem ich vor über einem Jahr Pi-Hole installiert habe und dann kaum verwendet habe, möchte ich jetzt den Sprung zu einem kompletten Setup machen. Also inklusive DHCP. Das Ganze sollte dann auch redundant ausgeführt werden, das heißt mindestens zwei Geräte. Nachdem ich ja vor kurzem ein Odroid H4 Plus gekauft und als komplett verschlüsseltes gespiegeltes ZFS-Boot Array aufgesetzt habe und als NAS betreiben werde, bietet sich dieses Gerät gut als Fall-Back Instanz an.

Das größere Ziel dahinter ist, dass ich endlich gescheite Zertifikate verwenden möchte. Eine passende Domain habe ich schon für diesen Zweck und ich es wäre natürlich nett, wenn ich nicht immer mit den ganzen IP-Adressen herumhantieren muss (Management by Spaghetti IP). Der Hauptgrund warum ich die alte Pi-Hole nie wirklich zur Gänze verwendet habe, war das fehlende Vertrauen in ein Einzelsetup ohne Rückfalloption. Aber das ist gar nicht so schwierig, wenn man zwei Geräte hat, die durchgehend laufen.

Setup

Die primäre Pi-Hole-Installation werde ich auf meinem HP Elitedesk G4 800 Mini einrichten und das Backup auf dem Odroid-NAS. Für die Installation werde ich das Proxmox Community Skript für Pi-Hole + Unbound verwenden.

Für die Installation führt man den folgenden Befehl in der Shell vom Proxmox Host aus:

bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/pihole.sh)"

Bestätigen:

Auf jeden Fall “Advanced Settings” auswählen…

… ansonsten bekommt Pi-Hole eine durch DHCP automatisch zugewiesene IP-Adresse. Das wäre nicht gut.

Unprivileged:

Passwort für rootfestlegen:

Als Hostnamen zb pihole1:

Damit kann man die Instanz einfach von der zweiten unterscheiden.

2 GB Speicher sollten ausreichen, vergrößern geht eh immer:

Ich werde jedoch zwei statt nur einen Kern vergeben, ich habe eh insgesamt sechs Kerne verfügbar mit dem i5-8400t:

512MB RAM sollten auch mehr als ausreichend sein:

Bei einem komplexeren Netzwerk, die richtige Bridge auswählen:

Hier wichtig DHCP mit einer statischen IP-Adresse ersetzen:

Dann noch Gateway eingeben und den APT-Cache leer lassen (außer man verwendet sowas natürlich). Ich habe noch IPv6 deaktiviert:

Jetzt braucht man ein Search Domain, diese wird nachher noch relevant, denn dann kann man einfach Hosts anpingen oder per SSH aufrufen, ohne dass man die ganzen Domainendungen dazuschreiben muss. Also einfach nas und dann wird automatisch die Search Domain hinzugefügt, zb nas.h.domainname.com. Die Subdomäne h (oder home wenn man es gerne länger hat) sorgt später wiederum dafür, dass man ein Wildcard Zertifikat für die gesamte Subdomain erstellen kann, sprich man kann dann beliebig viele Zertifikate für alle möglichen Seiten unter *.h.domainname.com verwenden. In diesem Blogbeitrag vom recht bekannten Youtuber “Wolfgang’s Channel” wird noch genauer darauf eingegangen und auch wie man DuckDNS verwenden kann, wenn man sich keine Domain kaufen möchte. Wie dem auch sei, an dieser Stelle kann man gleich die gewünschte Search Domain eintragen.

Das ist jetzt fast witzig:

Welchen DNS-Server soll der zukünftige DNS-Server haben? Aber für’s Setup brauchts wohl noch einen anderen DNS-Server. Ich belasse es bei Host.

Die MAC-Adresse lassen wir auch so wie sie ist:

Und auch Vlan lasse ich leer:

Die Tags habe ich auch gelöscht.

SSH-Root-Access ja:

Und natürlich dann den eigenen öffentlichen SSH-Schlüssel reinkopieren:

Wenn der Installer gesprächig sein soll, den “Verbose Mode” aktivieren:

Und noch einmal bestätigen und schon beginnt die Installation:

Dann wird man gefragt, ob man Unbound auch installieren möchte, mit y bestätigen:

Und nun wird man gefragt, ob man Unbound als rekursiven DNS Server (Default) oder als weiterleitenden DNS Server betreiben möchte:

Da ich mir nicht sicher war, habe ich dazu das Modell o3 befragt (Quelle):

Wir wollen also das also eh als rekursiven DNS Server. Also einfach mit Enter bestätigen (dann wird die Default Einstellung N genommen – Default ist immer großgeschrieben in der Kommandozeilen Konvention).

Und schon ist die Installation fertig:

Seite aufrufen:

Jetzt gibt es aber noch kein Administratorpasswort. Das fügen wir per SSH hinzu. Also als root-User einloggen. Dank SSH-Schlüssel-Hinterlegung ohne Passworteingabe und schon wird man vom Konsolen-Pi-Hole begrüßt:

Konfigurieren und Testen

Mit dem Befehl pihole setpassword kann man das Passwort für die Weboberfläche setzen:

Jetzt können wir Unbound testen mit:

dig @127.0.0.1 -p 5335 example.com +dnssec

Die Ausgabe sollte so aussehen:

; <<>> DiG 9.18.33-1~deb12u2-Debian <<>> @127.0.0.1 -p 5335 example.com +dnssec
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 62059
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 7, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 1232
;; QUESTION SECTION:
;example.com.			IN	A

;; ANSWER SECTION:
example.com.		300	IN	A	23.215.0.138
example.com.		300	IN	A	96.7.128.175
example.com.		300	IN	A	96.7.128.198
example.com.		300	IN	A	23.192.228.80
example.com.		300	IN	A	23.192.228.84
example.com.		300	IN	A	23.215.0.136
example.com.		300	IN	RRSIG	A 13 2 300 20250521082548 20250430022038 31463 example.com. BZbx/YUgG+gSRD3N+09SUNJsT2KsnlhHChRysznZkrXjIy++MWQHgIdf IsasbYjryJlon2jW6GpoT79zvJ6vXA==

;; Query time: 337 msec
;; SERVER: 127.0.0.1#5335(127.0.0.1) (UDP)
;; WHEN: Wed May 14 23:13:32 CEST 2025
;; MSG SIZE  rcvd: 243

Und wenn man den Befehl noch einmal ausführt, sollte die Antwort sofort kommen, denn die wurde jetzt gecacht.

Jetzt noch eine Abfrage für eine fehlerhafte Anfrage:

dig dnssec-failed.org @127.0.0.1 -p 5335 +dnssec

Hier sollte die Ausgabe so aussehen:

; <<>> DiG 9.18.33-1~deb12u2-Debian <<>> dnssec-failed.org @127.0.0.1 -p 5335 +dnssec
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 16526
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 1232
;; QUESTION SECTION:
;dnssec-failed.org.		IN	A

;; Query time: 1 msec
;; SERVER: 127.0.0.1#5335(127.0.0.1) (UDP)
;; WHEN: Wed May 14 23:21:07 CEST 2025
;; MSG SIZE  rcvd: 46

Jetzt können wir einmal die Blockierliste aktualisieren:

Jetzt kann man die zweite Instanz auf dem anderen Gerät mit den gleichen Einstellung (bis auf Hostnamen und IP-Adresse) installieren.

Jetzt ist ein guter Zeitpunkt für ein Backup von jedem LXC-Container und auch einen schnellen Snapshot auf pihole1.

Nebula Sync

Um die beiden Pi-Holes zusammenzuhalten, verwenden wir Nebula Sync. Hier ist auch noch eine umfangreichere Anleitung, jedoch als Docker Container. Das brauchen wir hier nicht, denn Nebula Sync ist in Go geschrieben und funktioniert daher wunderbar als Standalone Binary.

Diese Befehle installieren wget und tar (bei Debian/Ubuntu eh vorinstalliert), laden die aktuellste Binary für die richtige CPU-Architektur direkt in /usr/local/bin/nebula-sync, entpackt diese, macht sie ausführbar und löscht das Archiv:

apt update && apt install -y wget tar  # makes sure tar & wget exist
 
arch=$(dpkg --print-architecture)      # prints amd64, arm64, armhf …
 
# Fetch the latest tag name (e.g. v0.11.0)
ver=$(wget -qO- https://api.github.com/repos/lovelaze/nebula-sync/releases/latest \
       | grep '"tag_name"' | cut -d '"' -f4)
 
# Download the matching tarball and unpack just the binary
wget -O /tmp/nebula.tar.gz \
  https://github.com/lovelaze/nebula-sync/releases/download/${ver}/nebula-sync_${ver#v}_linux_${arch}.tar.gz :contentReference[oaicite:1]{index=1}
 
tar -xzf /tmp/nebula.tar.gz -C /usr/local/bin nebula-sync
chmod +x /usr/local/bin/nebula-sync
rm /tmp/nebula.tar.gz

Überprüfen:

ls -la /usr/local/bin/nebula-sync
nebula-sync --version

Jetzt /etc/nebula-sync.env erstellen mit:

PRIMARY=http://127.0.0.1|<TOKEN_1>
REPLICAS=http://<pihole2-ip-einfügen>|<TOKEN_2>
FULL_SYNC=false
RUN_GRAVITY=false
CRON=*/5 * * * *
SYNC_CONFIG_DNS=true
SYNC_CONFIG_SETTINGS=true

Die Tokens ersetzen, das geht so (Quelle1, Quelle2):

Auf “Configure app password” klicken…

… und kopieren. Dasselbe auf pihole2 und beide in die Umgebungsvariable eintragen.

Jetzt brauchen wir ein Systemd-Modul:

# /etc/systemd/system/nebula-sync.service
[Unit]
Description=Nebula-Sync job for Pi-hole HA
After=network-online.target
Wants=network-online.target
 
[Service]
Type=oneshot
EnvironmentFile=/etc/nebula-sync.env
ExecStart=/usr/local/bin/nebula-sync run --env-file /etc/nebula-sync.env

Und einen Systemd-Timer, der alle fünf Minuten startet:

# /etc/systemd/system/nebula-sync.timer
[Timer]
OnCalendar=*-*-* *:0/5:00
Persistent=true
# /etc/systemd/system/nebula-sync.timer
[Unit]
Description=Run Nebula-Sync every 5 minutes
 
[Timer]
OnCalendar=*-*-* *:0/5:00
Persistent=true
 
[Install]
WantedBy=timers.target

Weitere Befehle:

# pick up the new unit files
sudo systemctl daemon-reload
 
# start now and persist across reboots
sudo systemctl enable --now nebula-sync.timer
 
# verify it’s scheduled
systemctl status nebula-sync.timer
systemctl list-timers | grep nebula-sync
 
# run a dry-run sync for peace of mind
nebula-sync run --env-file /etc/nebula-sync.env

Hinweis: Die Configs wurden alle mithilfe von OpenAIs o3 Modell erstellt und beinhalteten zu Beginn auch Fehler.

Leider hat es bei, neben kleineren Fehlern in den Configs, nicht gleich funktioniert:

root@pihole1:~# nebula-sync run --env-file /etc/nebula-sync.env
2025-05-15T01:14:52+02:00 INF Starting nebula-sync v0.11.0
2025-05-15T01:14:52+02:00 INF Running sync mode=selective replicas=1
2025-05-15T01:14:52+02:00 INF Authenticating clients...
2025-05-15T01:14:53+02:00 INF Syncing teleporters...
2025-05-15T01:14:57+02:00 INF Invalidating sessions...
2025-05-15T01:14:57+02:00 FTL Sync failed error="sync teleporters: http://192.168.0.3/api/teleporter: unexpected status code: 403"

Der Bot meint, das ist by design und muss vorher aktiviert werden (Quelle1, Quelle2):

Lösung, auf pihole2 ausführen:

sudo pihole-FTL --config webserver.api.app_sudo true
sudo systemctl restart pihole-FTL

Bis auf den Hänger (siehe q und ^C) scheint es zu funktionieren:

Umstellen auf DHCP von Pi-Hole

Als nächstes müssen wir den DHCP Server auf pihole1 aktivieren, dank nebula-sync aber nur dort im partial Modus auch dort (bin erst danach draufgekommen, aber man muss nur die identischen DHCP Einstellungen auf der zweiten Instanz durchführen):

Achtung: Die höchste zu vergebene IP-Adresse darf nur 254, also entsprechend anpassen; aber Pi-Hole beschwert sich eh, wenn etwas nicht passt.

Damit sollte nun Pi-Hole die Tätigkeit des Routers übernehmen. Aber Moment, der Router weiß ja noch gar nichts davon. Dann loggen wir uns einmal in die “Kraxn” ein, bei mir ein wunderbarer “Compal Broadband Networks CH7465LG-LC” mit dem flotten Magenta Branding als “Fiber Box” weißgottwas benannt:

Das Trumm läuft zwar, aber ich habe mich in den knapp zehn Minuten meiner Auseinandersetzung mit dem Gerät vier Mal neu einloggen müssen. Klickt man irgendwo hin, dann kommt die Meldung “Ups” und irgendwas ist schief gegangen und schon ist man wieder bei dieser Login Seite mit den blöd dreinschauenden Stock-Foto-Leuten (die wahrscheinlich eh auch gerade ihr “Gewürgst” mit dem Router haben)…

Aber egal, es geht ja nicht um das Kastl – außer der Tatsache, dass man bei Magenta leider all in gehen muss, DNS-Server-Einstellungen gibt es gar nicht – sondern um das Umstellen auf Pi-Hole. Der erste Schritt ist dafür einmal die DHCP-Lease-Zeit zu verringern von einem Tag auf von mir aus 600 Sekunden. Dadurch geht die Umstellung schneller (oder ich verliere schneller Wlan am Laptop und Handy). Das geht so:

Ah, oder doch noch einmal einloggen… So, jetzt zamreißen, wir sind bald fertig mit dem Router. Zum fünften neunten (!) Mal einloggen:

Ich habe zuerst die Lease Zeit verringert:

Dann aber gleich abgedreht:

Achja und DHCPv6 ist nie gelaufen bei mir:

Eigentlich wollte ich noch die Erfolgsmeldung screenshotten, dass der DHCP-Server nun ausgeschaltet ist, aber hehe:

Schon geil dieses Trumm…

Jetzt heißt es hoffen und abwarten.

Compal sagt nein…

Ich habe mich dann noch viel zu lange mit dem ISP-Router herumgeschlagen, leider will der nicht IPv6 DHCP abschalten, unter keinen Umständen. Da hilft nur den Router in reinen Bridge Betrieb zu setzen. Aber das mache ich ein anderes Mal. Aber abgesehen davon, funktioniert das Setup bereits, redundante Pi-Holes.

Fazit

Das redundante Pi-Hole Setup läuft zumindest stabil. Pi-Hole lebt jeweils auf einem LXC-Container auf dem HP Elitedesk und als Backup auf dem Odroid H4 Plus. Die Einstellungen werden mit Nebula Sync alle fünf Minuten kopiert und das DNS-Blocking funktioniert auch wunderbar. Einzig IPv6 DHCP lässt sich mit gegebener Hardware nicht lösen. Da bleibt wohl nur der Weg zu Brigde-Modus. Im nächsten Beitrag geht’s dann ans Eingemachte, den Zertifikaten für’s Homelab!