4 02 AdGuard CT100
arnol edited this page 2026-08-08 12:32:56 +02:00
This file contains ambiguous Unicode characters

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, 12 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 1015s 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)