Pi-Hole und Unbound

Pi-Hole und Unbound – thinkkreativ Blogartikel

Eigentlich wollte ich „nur mal kurz“ einen Werbeblocker fürs ganze Haus einrichten. Am Ende ist daraus ein kleines Infrastruktur-Projekt geworden: Pi-hole als zentraler DNS-Werbeblocker, Unbound als eigener, rekursiver DNS-Resolver, das Ganze in einer Ubuntu-VM unter Proxmox, mit Docker, SSL, Home Assistant und hübschen ApexCharts.

In diesem Artikel zeige ich dir:

  • Warum ich Pi-hole nutze (und warum es sich lohnt)
  • Warum Unbound die perfekte Ergänzung ist
  • Wie das Ganze in einer Proxmox-VM läuft
  • Wie ich Pi-hole in Docker mit Port 8081 betreibe (weil Port 80 schon belegt war)
  • Wie Unbound optimiert wurde (DNSSEC, Cache, QNAME-Minimisation usw.)
  • Wie ich das Ganze mit einer ApexCharts-Karte in Home Assistant visualisiere

Warum Pi-hole?

Pi-hole ist ein DNS-basierter Werbeblocker für das gesamte Netzwerk. Im Gegensatz zu Browser-Plugins blockiert Pi-hole Werbung und Tracking direkt beim DNS-Request:

  • Werbung wird gar nicht erst geladen
  • Tracking-Domains laufen ins Leere
  • Alle Geräte im Netzwerk profitieren: iPhones, Smart-TVs, Tablets, IoT usw.

Das Ergebnis merkt man sofort: Seiten bauen sich schneller auf, YouTube fühlt sich „entschlackt“ an, und vor allem Kindergeräte sehen deutlich weniger Schrott.


Warum zusätzlich Unbound?

Pi-hole alleine ist schon gut – aber standardmäßig verwendet es externe DNS-Resolver wie Google (8.8.8.8) oder Cloudflare (1.1.1.1). Das hat zwei Nachteile:

  • Ein externer Anbieter sieht alle DNS-Anfragen deines Haushalts
  • Du bist von der Performance und Verfügbarkeit dieses einen Dienstes abhängig

Unbound löst beides elegant:

  • Unbound ist ein eigener rekursiver DNS-Resolver
  • Er fragt direkt bei den Root-Servern, TLD-Servern und autoritativen Nameservern
  • Er cached Antworten lokal (sehr viele Anfragen kommen mit 0–1 ms zurück)
  • Er nutzt DNSSEC zur Validierung und erhöht damit die Sicherheit
  • Kein großer DNS-Anbieter sieht mehr deinen kompletten Traffic

Die Kombination sieht so aus:

Geräte im LAN → Pi-hole (Blocker) → Unbound (Resolver) → Internet

Pi-hole filtert Werbung und Tracking, Unbound löst die übrig gebliebenen Anfragen selbstständig auf – schnell und datenschutzfreundlich.


Proxmox-Setup: Warum eine eigene VM?

Ich betreibe meine Dienste auf einem Proxmox-Host mit mehreren VMs. Für Pi-hole + Unbound habe ich eine eigene Ubuntu Server VM erstellt. Vorteile:

  • Saubere Trennung von Funktionen (Webserver, Home Assistant, DNS usw.)
  • Snapshots vor großen Änderungen möglich
  • Leichtes Backup der gesamten DNS-Infrastruktur
  • Im Notfall kann die VM auf einen anderen Host migriert werden

Die VM hat im LAN eine feste IP: 192.168.178.5. Auf dieser IP laufen:

  • mein Apache-Webserver (ThinkKreativ & Co.)
  • Home Assistant im Docker
  • Pi-hole + Unbound im Docker

Port 80 war schon belegt – Pi-hole auf 8081

Der erste Klassiker: Pi-hole möchte mit seinem Webinterface auf Port 80 laufen. Genau dort hängt aber bereits mein Apache, der u.a. thinkkreativ.de und andere Subdomains ausliefert.

Ergebnis beim ersten Versuch: Port-Konflikt, Pi-hole-Webinterface startet nicht.

Lösung: Pi-hole bekommt einfach einen anderen externen Port. Intern im Container läuft es weiter auf Port 80, nach außen mappe ich aber auf 8081:

ports:
  - "53:53/tcp"
  - "53:53/udp"
  - "8081:80/tcp"

Der Apache bleibt auf Port 80, Pi-hole ist nun über http://192.168.178.5:8081/admin erreichbar.


Pi-hole im Docker-Container

Das Herzstück ist der Docker-Container von Pi-hole. Die finale, funktionierende docker-compose.yml für Pi-hole sieht so aus:

version: "3"

services:
  pihole:
    container_name: pihole
    image: pihole/pihole:latest
    environment:
      TZ: 'Europe/Berlin'
      WEBPASSWORD: 'DEINPASSWORT'
      DNSMASQ_LISTENING: all
      FTLCONF_LOCAL_IPV4: 192.168.178.5
      PIHOLE_DNS_: "192.168.178.5#5335"   # Unbound als Upstream
      FTLCONF_REPLY_ADDR4: 192.168.178.5
      FTLCONF_SERVER_PORT: 53
    volumes:
      - ./etc-pihole:/etc/pihole
      - ./etc-dnsmasq.d:/etc/dnsmasq.d
    ports:
      - "53:53/tcp"
      - "53:53/udp"
      - "8081:80/tcp"
    restart: unless-stopped

Wichtig sind hier vor allem:

  • FTLCONF_LOCAL_IPV4: die LAN-IP der VM
  • PIHOLE_DNS_: hier sage ich Pi-hole, dass es Unbound auf 192.168.178.5#5335 als Upstream-DNS nutzen soll
  • Port-Mapping: 53 für DNS, 8081 für das Webinterface

Pi-hole GUI: Was sehe ich dort?

Nach der Installation zeigt die Pi-hole-Oberfläche u.a.:

  • DNS-Abfragen des Tages
  • Blockierte Domains
  • Blockrate (%)
  • Eine Liste der Geräte (Clients), die Pi-hole verwenden
  • „Recent Queries“ – also was gerade im Netz passiert

In der Query-Liste sind:

  • grüne Einträge → normal beantwortete Anfragen
  • rote Einträge → geblockte Domains (Werbung, Tracking, Telemetrie)

Besonders spannend: Man sieht, wie fleißig iCloud, Facebook, Smart-TVs & Co. im Hintergrund Daten abfragen – auch wenn man eigentlich gerade „nichts“ macht.


Unbound als eigener DNS-Resolver

Der Unbound-Container läuft ebenfalls separat im Docker:

services:
  unbound:
    image: mvance/unbound:latest
    container_name: unbound
    restart: unless-stopped
    ports:
      - "5335:53/tcp"
      - "5335:53/udp"
    volumes:
      - /opt/unbound/unbound.conf:/etc/unbound/unbound.conf:ro

Nach außen lauscht Unbound also auf 192.168.178.5:5335. Pi-hole spricht ihn intern über diese Adresse an.


Optimierte Unbound-Konfiguration – was da genau passiert

Die wahre Magie passierte in der unbound.conf. Hier die finale, optimierte Version:

server:
  ############################
  # Basis / Schnittstellen
  ############################
  verbosity: 1                 # 0 = ruhig, 1 = kurze Infos
  interface: 0.0.0.0           # auf allen IPv4-Interfaces lauschen
  port: 53

  do-ip4: yes
  do-ip6: no                   # IPv6 im LAN (noch) nicht aktiv → aus
  do-udp: yes
  do-tcp: yes

  num-threads: 2               # reicht für meinen Server völlig
  so-reuseport: yes
  edns-buffer-size: 1232       # sicherer Wert gegen Fragmentierung

  ############################
  # Sicherheit & Privacy
  ############################
  hide-identity: yes           # verrät nicht, dass es Unbound ist
  hide-version: yes            # Versionsnummer nicht preisgeben
  harden-glue: yes
  harden-dnssec-stripped: yes
  harden-referral-path: yes
  qname-minimisation: yes      # sendet nur so viel Name wie nötig
  aggressive-nsec: yes         # nutzt NSEC/NSEC3, um Nicht-Existenz zu cachen

  ############################
  # Cache / Performance
  ############################
  msg-cache-size: 64m          # Antwort-Cache
  rrset-cache-size: 128m       # Resource-Record-Cache
  cache-min-ttl: 600           # min. 10 Minuten im Cache behalten
  cache-max-ttl: 86400         # max. 1 Tag
  prefetch: yes                # beliebte Einträge vor Ablauf neu auflösen
  prefetch-key: yes            # auch DNSSEC-Keys prefetchen

  ############################
  # Logging
  ############################
  log-queries: no              # keine Einzel-Queries loggen
  log-replies: no
  use-syslog: yes
  logfile: ""

  ############################
  # DNSSEC / Root-Trust
  ############################
  auto-trust-anchor-file: "/var/lib/unbound/root.key"
  root-hints: "/var/lib/unbound/root.hints"

Was bedeutet das konkret?

  • QNAME-Minimisation (qname-minimisation: yes): Unbound fragt nicht sofort „www.google.com“ bei jedem Server an, sondern arbeitet sich stückweise vor (. → com → google.com → www.google.com). Jeder Server sieht nur so viel wie nötig. Das erhöht die Privatsphäre.
  • DNSSEC (auto-trust-anchor-file + root-hints): Antworten werden kryptographisch geprüft. Manipulierte DNS-Antworten (z.B. durch Spoofing) fallen auf.
  • Cache-Größe ~ 200 MB (msg-cache-size + rrset-cache-size): Viele Antworten bleiben im RAM, häufig genutzte Domains (Google, Apple, CDN-Hosts usw.) kommen quasi immer aus dem Cache. Ergebnis: dig google.com meldet oft Query time: 0 msec.
  • Aggressives Caching (cache-min-ttl, cache-max-ttl, prefetch): Unbound hält Antworten mindestens 10 Minuten (auch wenn die TTL kleiner ist) und maximal einen Tag. Besonders häufig genutzte Einträge werden kurz vor Ablauf im Hintergrund neu aufgelöst. Das sorgt für konstant schnelle Antwortzeiten.
  • EDNS-Buffer optimiert (edns-buffer-size: 1232): Reduziert Probleme mit Fragmentierung und Firewalls, die große UDP-Pakete blocken.
  • IPv6 abgeschaltet (do-ip6: no): Solange im LAN kein durchgängig sauberes IPv6 genutzt wird, ist das die stabilere Variante.
  • Logging reduziert (log-queries: no): Einzelne DNS-Queries werden nicht von Unbound geloggt. Die wichtigen Infos sehe ich ohnehin im Pi-hole. Das spart IO, Platz und erhöht die Privatsphäre.

Nach dieser Optimierung fühlt sich DNS wirklich „instant“ an – vor allem, wenn man die Antwortzeiten mit dig misst.


ApexCharts in Home Assistant: Pi-hole schön visualisieren

Damit das Ganze nicht nur technisch funktioniert, sondern auch hübsch aussieht, habe ich Pi-hole in Home Assistant eingebunden und mit der apexcharts-card eine kleine Übersicht gebaut.

Die Sensoren dazu kommen aus der Pi-hole-Integration, z.B.:

  • sensor.pi_hole_dns_abfragen
  • sensor.pi_hole_blockierte_anzeigen
  • sensor.pi_hole_anteil_blockierter_anzeigen (Blockrate in %)

Hier das Template für die ApexCharts-Karte:

type: custom:apexcharts-card
header:
  show: true
  title: Pi-hole – Gesamtübersicht
  show_states: true
  colorize_states: true
graph_span: 24h
span:
  start: day
now:
  show: true
chart:
  height: 260
  toolbar:
    show: false
yaxis:
  - id: main
    title: 'Anfragen'
    decimals: 0
  - id: blockrate
    opposite: true
    title: 'Blockrate %'
    min: 0
    max: 100
    decimals: 0
series:
  - entity: sensor.pi_hole_dns_abfragen
    name: 'DNS-Abfragen'
    type: column
    color: '#4499EE'
    group_by:
      duration: 10min
      func: max
    yaxis_id: main
  - entity: sensor.pi_hole_blockierte_anzeigen
    name: 'Blockierte Anzeigen'
    type: column
    color: '#FFAA33'
    group_by:
      duration: 10min
      func: max
    yaxis_id: main
  - entity: sensor.pi_hole_anteil_blockierter_anzeigen
    name: 'Blockrate %'
    type: line
    color: '#FF4F4F'
    stroke_width: 2
    group_by:
      duration: 10min
      func: max
    yaxis_id: blockrate
legend:
  show: true
  formatter: |
    EVAL:(w, { seriesName, value }) => {
      if (seriesName === 'Blockrate %') {
        return `${seriesName}: ${Math.round(value)} %`;
      }
      return `${seriesName}: ${Math.round(value)}`;
    }

Damit bekomme ich auf einen Blick:

  • Wie viele DNS-Abfragen es heute gab
  • Wie viele davon geblockt wurden
  • Wie hoch die Blockrate in % ist
  • Wie sich das alles über den Tag verteilt

Fazit: Einmal Pi-hole + Unbound, nie wieder ohne

Die Kombination aus Pi-hole und Unbound in einer Proxmox-VM hat sich für mich voll gelohnt:

  • Weniger Werbung und Tracking im gesamten Haushalt
  • Schnellere DNS-Antworten dank lokalem Cache und Prefetching
  • Mehr Datenschutz, weil kein externer Anbieter alle DNS-Queries sieht
  • Saubere Trennung auf einer dedizierten VM und leichtes Backup
  • Schöne Visualisierung in Home Assistant durch ApexCharts

Wenn du also sowieso einen Proxmox-Host oder einen kleinen Homeserver betreibst: Pi-hole + Unbound solltest du dir unbedingt anschauen. Es ist eines dieser Projekte, die man einmal einrichtet – und danach fragt man sich, wie man vorher ohne leben konnte.