Pi-hole ist eine der bekanntesten Lösungen, um Werbung, Tracking und unerwünschte Domains bereits auf DNS-Ebene aus dem eigenen Netzwerk herauszufiltern. In Kombination mit dnscrypt-proxy lässt sich das Ganze noch erweitern: DNS-Anfragen werden zunächst von Pi-hole gefiltert und anschließend über einen verschlüsselten DNS-Upstream weitergeleitet.
In diesem Beitrag zeige ich meinen Docker-Stack, der Pi-hole und dnscrypt-proxy kombiniert. Neben festen IPv4- und IPv6-Adressen bringt der Stack vorkonfigurierte Blocklisten, persistente Daten und eine optionale Integration in Traefik mit.
Das vollständige Projekt befindet sich auf GitHub:
https://github.com/happylippo/pihole-stack
Was macht der Stack?
Der grundlegende Aufbau ist relativ einfach:
Client
│
│ DNS :53
▼
Pi-hole
172.18.3.2
│
│ DNS :5053
▼
dnscrypt-proxy
172.18.3.3
│
│ verschlüsselte DNS-Anfrage
▼
Quad9
│
▼
Internet
Pi-hole ist dabei der DNS-Server für die Geräte im lokalen Netzwerk.
Anfragen werden zunächst gegen die in Pi-hole hinterlegten Blocklisten geprüft. Erlaubte DNS-Anfragen gehen anschließend nicht direkt an einen gewöhnlichen DNS-Server, sondern an den ebenfalls im Stack laufenden dnscrypt-proxy.
Verwendete Docker-Images
Der Stack verwendet bewusst nur zwei zentrale Container:
pihole/pihole:latest
zydou/dnscrypt:latest
Pi-hole übernimmt DNS-Filterung und Verwaltung, während dnscrypt-proxy für die Kommunikation mit den externen DNS-Resolvern zuständig ist.
Die Konfiguration und persistenten Daten befinden sich außerhalb der Container. Dadurch können die Container aktualisiert oder neu erstellt werden, ohne die Pi-hole-Konfiguration zu verlieren.
Eigenes Dual-Stack-Netzwerk
Pi-hole und dnscrypt-proxy befinden sich in einem gemeinsamen Docker-Netzwerk namens dns.
Dabei werden sowohl IPv4 als auch IPv6 verwendet:
IPv4: 172.18.3.0/24
IPv6: fd00:dead:beef:30::/64
Die Container erhalten feste Adressen:
| Service | IPv4 | IPv6 |
|---|---|---|
| Pi-hole | 172.18.3.2 | fd00:dead:beef:30::2 |
| dnscrypt-proxy | 172.18.3.3 | fd00:dead:beef:30::3 |
Die festen Adressen sind hier durchaus sinnvoll. Pi-hole kann dadurch dnscrypt-proxy immer unter derselben Adresse erreichen.
Die Werte lassen sich bei Bedarf über die .env-Datei ändern.
DNS über dnscrypt-proxy
Pi-hole verwendet dnscrypt-proxy als Upstream-DNS-Server.
dnscrypt-proxy lauscht innerhalb des Docker-Netzwerks auf:
172.18.3.3:5053
Meine dnscrypt-proxy.toml verwendet Quad9 als Resolver:
ipv6_servers = true
listen_addresses = [
'172.18.3.3:5053'
]
server_names = [
'quad9-dnscrypt-ip6-filter-pri',
'quad9-dnscrypt-ip4-filter-pri'
]
require_dnssec = true
require_nolog = true
require_nofilter = false
Zusätzlich werden DNSSEC-fähige Resolver vorausgesetzt.
Query- und NXDOMAIN-Logs können ebenfalls geschrieben werden:
[query_log]
file = '/var/log/dnscrypt-proxy/query.log'
[nx_log]
file = ‚/var/log/dnscrypt-proxy/nx.log‘
Diese Verzeichnisse werden vom Stack persistent auf dem Docker-Host gespeichert.
Vorgefertigte Blocklisten
Zusätzlich enthält das Repository eine Sammlung externer Pi-hole-Blocklisten.
Sie befindet sich unter:
config/pihole-blocklist.txt
Enthalten sind unter anderem Listen für:
- Werbung
- Tracking
- Malware
- Phishing
- Microsoft-Telemetrie
- Social-Media-Dienste
- Streaming-Dienste
- Adult-Inhalte
- verschiedene deutschsprachige Filterlisten
Die Liste kann selbstverständlich vor der Installation angepasst werden.
Wer beispielsweise bestimmte Kategorien nicht blockieren möchte, entfernt die entsprechenden URLs einfach aus der Datei.
Blocklisten automatisch in Pi-hole importieren
Damit die Listen nicht alle manuell über die Pi-hole-Weboberfläche eingetragen werden müssen, enthält das Repository zusätzlich:
scripts/init-blocklists.sh
Das Skript liest die Blocklisten-Datei ein und trägt die URLs in die Adlist-Datenbank von Pi-hole ein.
Anschließend wird:
pihole -g
ausgeführt.
Pi-hole lädt dadurch die eingetragenen Listen herunter und baut seine Gravity-Datenbank auf.
Damit die Initialisierung nicht bei jedem Start erneut durchgeführt wird, legt das Skript anschließend eine Marker-Datei an:
/etc/pihole/.blocklists-initialized
So bleibt der normale Containerstart schlank.
Pi-hole als DNS-Server im LAN
Der Stack veröffentlicht Port 53 sowohl für TCP als auch UDP:
ports:
- "0.0.0.0:53:53/tcp"
- "0.0.0.0:53:53/udp"
- "[::]:53:53/tcp"
- "[::]:53:53/udp"
Im Router beziehungsweise DHCP-Server muss anschließend die IP-Adresse des Docker-Hosts als DNS-Server verteilt werden.
Die Clients senden ihre DNS-Anfragen damit an den Docker-Host, Docker leitet sie an Pi-hole weiter und Pi-hole verwendet anschließend dnscrypt-proxy als Upstream.
Problem: Port 53 ist bereits belegt
Gerade bei Ubuntu kann es passieren, dass der Stack zunächst nicht startet:
Bind for 0.0.0.0:53 failed: port is already allocated
Häufig ist systemd-resolved dafür verantwortlich.
Zunächst lässt sich prüfen, wer Port 53 verwendet:
sudo ss -lntup | grep ':53 '
Taucht dort beispielsweise 127.0.0.53:53 beziehungsweise systemd-resolved auf, kann der lokale DNS-Stub deaktiviert werden.
Dazu:
sudo nano /etc/systemd/resolved.conf
und folgende Einstellung setzen:
[Resolve]
DNSStubListener=no
Anschließend:
sudo systemctl restart systemd-resolved
Damit der Host selbst weiterhin DNS-Namen auflösen kann, sollte außerdem geprüft werden, wohin /etc/resolv.conf zeigt.
Eine mögliche Konfiguration ist:
sudo ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.conf
Danach erneut:
sudo ss -lntup | grep ':53 '
Port 53 sollte nun für Pi-hole zur Verfügung stehen.
Es ist dabei normalerweise nicht notwendig, systemd-resolved vollständig zu deaktivieren.
Pi-hole hinter Traefik
Für die Weboberfläche befindet sich Pi-hole zusätzlich in meinem bereits vorhandenen externen Docker-Netzwerk:
proxy
Damit kann Traefik direkt mit dem Pi-hole-Webserver kommunizieren.
Der DNS-Dienst selbst läuft weiterhin über Port 53. Traefik wird ausschließlich für die HTTP-/HTTPS-Weboberfläche verwendet.
Der Stack enthält dafür entsprechende Traefik-Labels und unterstützt unter anderem meine dynamischen Middlewares:
default@file
authentik@file
Damit lässt sich beispielsweise Authentik vor die Pi-hole-Weboberfläche schalten.
Hinweis: Die konkrete Traefik-Konfiguration muss natürlich an die eigene Umgebung angepasst werden. Wer Traefik nicht verwendet, kann die entsprechenden Labels und das zusätzliche proxy-Netzwerk entfernen.
Installation
Zunächst das Repository klonen:
git clone https://github.com/happylippo/pihole-stack.git
cd pihole-stack
Danach die Beispielkonfiguration kopieren:
cp .env.example .env
Jetzt sollte insbesondere das Pi-hole-Passwort geändert werden.
Anschließend lässt sich die Compose-Konfiguration kontrollieren:
docker compose config
Wenn alles passt:
docker compose pull
docker compose up -d
Den Status der Container kann man anschließend mit:
docker compose ps
kontrollieren.
Die Logs stehen über:
docker compose logs -f
zur Verfügung.
Blocklisten initialisieren
Nach dem ersten erfolgreichen Start können die vorbereiteten Blocklisten importiert werden:
docker compose exec pihole bash /scripts/init-blocklists.sh
Je nach Anzahl und Größe der verwendeten Listen kann der anschließende Gravity-Aufbau etwas dauern.
Ob die Initialisierung bereits durchgeführt wurde, lässt sich anhand der Marker-Datei kontrollieren:
docker compose exec pihole \
ls -l /etc/pihole/.blocklists-initialized
DNS testen
Nach der Installation sollte zunächst geprüft werden, ob Pi-hole DNS-Anfragen beantwortet.
Von einem anderen Rechner im Netzwerk beispielsweise:
nslookup example.com IP-DES-DOCKER-HOSTS
oder unter Linux:
dig @IP-DES-DOCKER-HOSTS example.com
Anschließend lohnt sich ein Blick in das Query Log von Pi-hole. Dort sollten die DNS-Anfragen des Clients erscheinen.
Auch dnscrypt-proxy kann kontrolliert werden:
docker compose logs dnscrypt
Damit lässt sich nachvollziehen, ob der verschlüsselte Upstream erfolgreich gestartet wurde.
Updates
Da Konfiguration und Pi-hole-Daten persistent gespeichert werden, ist das Aktualisieren des Stacks unkompliziert:
docker compose pull
docker compose up -d
Alte Images können anschließend optional entfernt werden:
docker image prune
Fazit
Die Kombination aus Pi-hole und dnscrypt-proxy ist für mich eine interessante Alternative zu einem einfachen Pi-hole-Setup.
Pi-hole übernimmt die zentrale DNS-Filterung für das Netzwerk, während dnscrypt-proxy die Kommunikation mit den externen Resolvern übernimmt. Durch Docker bleibt die Installation reproduzierbar und lässt sich sauber von anderen Diensten trennen.
Besonders praktisch finde ich bei diesem Stack:
- Pi-hole und dnscrypt-proxy als getrennte Container
- feste IPv4- und IPv6-Adressen
- eigenes internes DNS-Netzwerk
- persistente Konfiguration
- vorbereitete Blocklisten
- automatisierter Blocklisten-Import
- Quad9 als vorkonfigurierter DNSCrypt-Upstream
- optionale Traefik- und Authentik-Integration
Der komplette Stack inklusive compose.yml, .env.example, dnscrypt-Konfiguration und Blocklisten steht auf GitHub zur Verfügung:
