Table of contents
- CT 100 — AdGuard Home — Rebuild-Runbook
- 1. Container erstellen (via Proxmox Community Script)
- 2. Web-Wizard (einmalig, interaktiv unter http://<LAN_IP_ADGUARD>:3000 bzw. später Port 80)
- 3. Conditional Forwarding für fritz.box
- 4. Filterlisten
- 5. DNS-Rewrites (interne Kurznamen)
- 6. Passwort-Reset (falls nötig)
- 7. Fritzbox-DHCP umstellen (manueller Schritt am Router, kein CLI)
- 8. IPv6-DNS aktivieren
- 9. Query-Log-Retention (der "stille Wachser")
- 10. Fallback-Upstream: Cloudflare
- Verifikation
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
CT 100 — AdGuard Home — Rebuild-Runbook
1. Container erstellen (via Proxmox Community Script)
Interaktiv am PVE-Host ausführen (whiptail-Dialog):
bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/adguard.sh)"
⚠️ NICHT TUN: adguardhome.sh als Dateiname verwenden — 404. Der korrekte Dateiname im Repo ist ct/adguard.sh.
Einstellungen im Advanced-Install-Dialog:
- Unprivileged, Debian <DEBIAN_RELEASE>
- 1 CPU-Core, 1024 MiB RAM (Standard 512 MiB reicht nicht für die großen Filterlisten, siehe unten), 2 GB Disk
- Bridge
vmbr0, statische IPv4<LAN_IP_ADGUARD>/24 - IPv6: none (Container bekommt trotzdem automatisch SLAAC-Adressen, unkritisch)
- Timezone Europe/Berlin, kein HTTP-Proxy
Falls mit 512 MiB angelegt, nachträglich erhöhen:
pct set 100 --memory 1024
2. Web-Wizard (einmalig, interaktiv unter http://<LAN_IP_ADGUARD>:3000 bzw. später Port 80)
- Admin-User:
root(oder eigener Name) - Passwort: eigenes wählen
- Upstream-DNS:
https://dns10.quad9.net/dns-query(Quad9 DoH)
Alle folgenden Schritte patchen /opt/AdGuardHome/AdGuardHome.yaml direkt (kein Web-UI-Login nötig, da Root-Zugriff auf den Container besteht). Immer:
pct exec 100 -- systemctl stop AdGuardHome
# Datei anpassen (siehe unten)
pct exec 100 -- systemctl start AdGuardHome
⚠️ NICHT TUN: die Config bei laufendem Dienst editieren — Änderungen werden beim nächsten internen Save von AdGuard überschrieben.
3. Conditional Forwarding für fritz.box
In dns: → upstream_dns: ergänzen:
upstream_dns:
- https://dns10.quad9.net/dns-query
- '[/fritz.box/]<LAN_IP_ROUTER>'
Private Reverse-DNS aktivieren (für Gerätenamen im Query-Log):
local_ptr_upstreams:
- <LAN_IP_ROUTER>
(Im Web-UI: Einstellungen → DNS-Einstellungen → Private Reverse-DNS-Server aktivieren, <LAN_IP_ROUTER>, privates Subnetz <LAN_SUBNET>)
4. Filterlisten
filters:-Block (Filter-IDs müssen eindeutig sein, 1–2 sind Standard-AdGuard-Defaults):
filters:
- enabled: true
url: https://adguardteam.github.io/HostlistsRegistry/assets/filter_1.txt
name: AdGuard DNS filter
id: 1
- enabled: false
url: https://adguardteam.github.io/HostlistsRegistry/assets/filter_2.txt
name: AdAway Default Blocklist
id: 2
- enabled: true
url: https://raw.githubusercontent.com/hagezi/dns-blocklists/main/adblock/pro.txt
name: HaGeZi's Pro Blocklist
id: 3
- enabled: true
url: https://raw.githubusercontent.com/hagezi/dns-blocklists/main/adblock/tif.txt
name: HaGeZi's Threat Intelligence Feeds
id: 4
- enabled: true
url: https://filters.adtidy.org/extension/chromium/filters/11.txt
name: AdGuard Mobile Ads filter
id: 5
- enabled: true
url: https://adguardteam.github.io/HostlistsRegistry/assets/filter_53.txt
name: AWAvenue Ads Rule
id: 7
- enabled: true
url: https://adguardteam.github.io/HostlistsRegistry/assets/filter_59.txt
name: AdGuard DNS Popup Hosts filter
id: 8
whitelist_filters:
- enabled: true
url: https://raw.githubusercontent.com/hagezi/dns-blocklists/main/adblock/whitelist-referral.txt
name: HaGeZi's Allowlist Referral
id: 6
⚠️ NICHT TUN (bewusst weggelassen, nicht aus Versehen): oisd NSFW, HaGeZi Anti-Piracy, ShadowWhisperer Dating List, HaGeZi Safesearch Not Supported (reine Content-Filter, kein Ad-Blocking-Zweck), HaGeZi DynDNS-Blocklist (Konflikt mit eigenem STRATO-DynDNS), HaGeZi Encrypted DNS/VPN/TOR/Proxy Bypass (Risiko für den Firmen-VPN-Tunnel der Ehefrau), Dandelion Sprout's Anti-Malware List + ShadowWhisperer's Malware List (redundant zu HaGeZi TIF, nur RAM-Verschwendung).
5. DNS-Rewrites (interne Kurznamen)
rewrites:
- domain: nas.pve
answer: <LAN_IP_NAS>
enabled: true
- domain: adguard.pve
answer: <LAN_IP_ADGUARD>
enabled: true
⚠️ NICHT TUN: das Feld enabled: true weglassen. AdGuard Home ergänzt es beim nächsten Neustart automatisch mit dem Default enabled: false — der Rewrite bleibt dann wirkungslos (Query geht bis zu den Root-DNS-Servern durch, NXDOMAIN), ohne erkennbaren Fehler in der Config selbst.
6. Passwort-Reset (falls nötig)
AdGuard speichert nur den bcrypt-Hash, keine Wiederherstellung möglich — neues Passwort setzen statt altes wiederherstellen:
pct exec 100 -- apt-get install -y apache2-utils # für htpasswd -B
NEWPASS="<neues Passwort>"
HASH=$(pct exec 100 -- htpasswd -bnBC 10 "" "$NEWPASS" | tr -d ':\n')
# In AdGuardHome.yaml, Zeile "password:" unter users: ersetzen durch $HASH
Der von htpasswd -B erzeugte Hash hat $2y$-Präfix statt AdGuards eigenem $2a$ — wird von Go/AdGuard-Home-bcrypt trotzdem korrekt akzeptiert.
7. Fritzbox-DHCP umstellen (manueller Schritt am Router, kein CLI)
Fritzbox-Oberfläche → Heimnetz → Netzwerk → Netzwerkeinstellungen → IPv4-Adressen bearbeiten → Abschnitt DNS-Server → „Andere DNSv4-Server verwenden" → Bevorzugter DNSv4-Server: <LAN_IP_ADGUARD>.
⚠️ NICHT TUN (verworfene Variante): Client fragt die Fritzbox, die AdGuard als Upstream nutzt. Stattdessen: Client fragt AdGuard direkt (wie oben), Fritzbox nur noch für fritz.box-Auflösung via Conditional Forwarding.
8. IPv6-DNS aktivieren
Standardmäßig lauscht AdGuard Home nur auf IPv4 (bind_hosts: [0.0.0.0]), obwohl der Container per SLAAC auch IPv6-Adressen bekommt — DNS-Anfragen über IPv6 werden dann gar nicht erst beantwortet, unabhängig davon, was die Fritzbox verteilt.
dns:
bind_hosts:
- 0.0.0.0
- "::"
⚠️ NICHT TUN: direkt vom PVE-Host aus testen, falls der selbst kein SLAAC-IPv6 auf der Bridge hat (ip -6 addr show vmbr0 prüfen — meist nur Link-Local vorhanden). Test stattdessen von einem Container aus, der eine globale IPv6-Adresse hat (z. B. pct exec 101 -- dig @<ULA-Adresse> google.com).
Manueller Schritt an der Fritzbox: Heimnetz → Netzwerk → Netzwerkeinstellungen → IPv6-Adressen bearbeiten → DNS-Server → eigenen DNSv6-Server eintragen. Die ULA-Adresse verwenden (fd23:..., per ip -6 addr show eth0 im Container ermitteln — steht als "scope global dynamic mngtmpaddr"), nicht die GUA (2a02:...) — die ULA bleibt stabil, die GUA kann sich bei einem ISP-Präfixwechsel ändern und die Konfiguration stillschweigend brechen.
9. Query-Log-Retention (der "stille Wachser")
Der netzwerkweite Resolver protokolliert jede DNS-Anfrage aus dem gesamten LAN — genau der
Log-Typ, der unbemerkt wächst, weil niemand ihn täglich ansieht. Bei diesem Durchlauf gefunden:
querylog.json bei mehreren hundert MB, Retention stand auf interval: 90d (Standard-Vorschlag von
AdGuard Home, nie bewusst gesetzt).
# /opt/AdGuardHome/AdGuardHome.yaml
querylog:
interval: 7d # vorher: 90d
pct exec 100 -- systemctl stop AdGuardHome
pct exec 100 -- sed -i 's/interval: 90d/interval: 7d/' /opt/AdGuardHome/AdGuardHome.yaml
pct exec 100 -- systemctl start AdGuardHome
⚠️ NICHT TUN: nach dem Ändern des Intervalls erwarten, dass die alte, große Datei sofort
schrumpft. AdGuard rotiert beim nächsten Start die bisherige Datei nach querylog.json.1
(Standard-Log-Rotationsmuster) und beginnt eine neue, leere querylog.json — die Altlast
bleibt als .1-Datei liegen, bis der nächste Rotationszyklus sie ersetzt. Für sofortige
Platzrückgewinnung die .1-Datei manuell löschen (reine Altdaten, keine laufende Datei):
pct exec 100 -- rm /opt/AdGuardHome/data/querylog.json.1. DNS-Auflösung/Blocking läuft davon
komplett unberührt weiter (dig-Verifikation direkt danach: Blocking + normale Auflösung beide
sofort wieder da).
Nachtrag: size_memory täuscht einen hängenden Log vor
AdGuards querylog.size_memory (Standard 1000) puffert Einträge im Arbeitsspeicher und schreibt sie
erst gebündelt auf die Platte — die Datei auf der Platte kann deshalb minutenlang unverändert wirken,
obwohl AdGuards eigene Web-UI (die direkt aus dem Live-Puffer liest) völlig normal weiterläuft. Kein
Bug, keine Aktion nötig — vor einem Neustart erst im UI selbst gegenprüfen, nicht nur die Datei-mtime.
Realer Vorfall, unabhängig davon: querylog.json wuchs innerhalb weniger Tage deutlich schneller
als üblich, und der AdGuard-Prozess peakte dabei nahe ans Container-Speicherlimit heran. Ursache:
nicht ein AdGuard-Problem selbst, sondern ein Notify-Flood eines separaten Docker-Image-Watchers
auf dem Monitoring-Container (siehe 08-monitoring.md) — die Registry-Abfragen dieses Watchers allein
machten einen Großteil der geloggten Zeilen aus, zeitlich exakt deckungsgleich mit dem Vorfall. Nach
dessen Behebung fiel die Anfragerate um mehr als eine Größenordnung. Lehre: ein
Ressourcen-Ausreißer bei AdGuard muss nicht an AdGuard selbst liegen — erst per IP/QH-Auswertung
des Query-Logs den tatsächlichen Verursacher finden (python3 über die JSON-Zeilen, Counter nach
Client und Domain), bevor an CPU/RAM-Zuteilung gedreht wird.
10. Fallback-Upstream: Cloudflare
Anlass: In der Nacht war der primäre Upstream (Quad9 DoH) in zwei Fenstern stark
degradiert — ~25 % der Anfragen ohne Antwort, teils 10–15s Latenz, ein Ausreißer 235s. Der
Internetanschluss selbst lief laut Router-Ereignisprotokoll durchgehend stabil (kein Reconnect),
das Problem lag isoliert am Quad9-Pfad. Root-Cause per Query-Log-Auswertung (Upstream-Feld
leer = keine Antwort) und Router-Ereignisprotokoll verifiziert, nicht vermutet.
# /opt/AdGuardHome/AdGuardHome.yaml
dns:
fallback_dns:
- https://1.1.1.1/dns-query
- https://1.0.0.1/dns-query
Bewusst fallback_dns, nicht ein zweiter upstream_dns-Eintrag. AdGuard behandelt
upstream_dns je nach upstream_mode als Load-Balancing-/Parallel-Pool — ein zweiter Eintrag
dort hätte Cloudflare gleichberechtigt mitbefragt, nicht nur bei Ausfall. fallback_dns springt
dagegen ausschließlich ein, wenn keiner der primären Upstreams rechtzeitig antwortet — genau das
Verhalten, das in der Nacht gefehlt hat.
Cloudflares DoH-Endpunkt bewusst per IP-Literal (1.1.1.1/1.0.0.1) statt Hostname
eingetragen — vermeidet eine zusätzliche Abhängigkeit von bootstrap_dns, um den
Fallback-Endpunkt selbst aufzulösen (der bestehende bootstrap_dns-Block ist ohnehin nur für
Quad9s Hostname-Endpunkt nötig).
Übliche stop/edit/start-Reihenfolge (Abschnitt 2) eingehalten. Verifiziert: nach Neustart
normale Auflösung, Conditional Forwarding, DNS-Rewrite und Ad-Blocking alle unverändert korrekt
(dig-Vierfachtest, siehe unten).
Verifikation
dig @<LAN_IP_ADGUARD> doubleclick.net # sollte 0.0.0.0 liefern (geblockt)
dig @<LAN_IP_ADGUARD> google.com # normale Antwort
dig @<LAN_IP_ADGUARD> fritz.box # <LAN_IP_ROUTER> (Conditional Forwarding)
dig @<LAN_IP_ADGUARD> nas.pve # <LAN_IP_NAS> (Rewrite)
Rebuild-Runbooks
Aufbau
- Übersicht
- PVE-Host
- AdGuard · CT100
- NAS · CT101
- Management-Desktop · CT102
- Vaultwarden · CT103
- VPS · STRATO
- Backup & Restore
- Monitoring · CT104
- Gast-Zugang
Referenz