Eigener DNS für unterwegs: AdGuard Home mit DoH, DoT und DoQ auf dem VPS
Teil 9 der Reihe Server im Netz: AdGuard Home auf dem VPS als verschlüsselter DNS-Server für unterwegs. Mit Wildcard-Zertifikat per lego, Client-IDs gegen offene Resolver, Caddy für DoH und zwei Fallen aus dem Test.
Geprüft am 05.11.2026 · für AdGuard Home 0.107.80, lego 5.6.0, Caddy 2.11.7
Mit KI erstellt – redaktionelle Prüfung ausstehend
WerbelinksMit * markierte Links sind Werbelinks: Bei einem Kauf erhalten wir eine Provision, der Preis bleibt gleich. Als Amazon-Partner verdiene ich an qualifizierten Verkäufen. Mehr dazu

Im Heimnetz filtert vielleicht schon ein Pi-hole oder AdGuard Home Werbung und Tracker. Unterwegs im Mobilfunk oder im Hotel-WLAN ist der Schutz weg, und die DNS-Anfragen laufen unverschlüsselt über fremde Server. Ein eigener DNS-Server auf dem VPS ändert das: Er nimmt nur verschlüsselte Anfragen über DNS-over-TLS (DoT), DNS-over-HTTPS (DoH) und DNS-over-QUIC (DoQ) an, filtert wie zu Hause und beantwortet nur Geräte, die er kennt. Diese Anleitung richtet AdGuard Home dafür ein, holt per DNS-01 ein Wildcard-Zertifikat für die Geräte-Kennungen und zeigt zwei Fallen aus dem Test: eine, bei der der eigene Name während der Zertifikatsprüfung verschwindet, und eine, bei der AdGuard Home gar nicht erst startet.
Dies ist Teil 9 der Reihe „Server im Netz“. Als Beispiel dient ein netcup-VPS mit Ubuntu und Docker, auf dem Caddy die Domain sedv-lab.de mit HTTPS ausliefert. Der DNS-Server bekommt den Namen dns.sedv-lab.de.
Voraussetzungen
- Der VPS aus Teil 4 mit Caddy im Host-Netz und Docker
- Eine Domain, deren DNS-Zone Sie über eine API ändern können, im Beispiel das klassische DNS von netcup mit API-Schlüssel aus dem Kundenkontrollpanel
- Freie Ports 853/TCP und 853/UDP auf dem VPS
- Ein Gerät zum Testen, etwa ein iPhone, oder ein Linux-Rechner mit
kdigaus dem Paketknot-dnsutils
So erkennt AdGuard Home Ihre Geräte
Verschlüsseltes DNS hat einen Vorteil gegenüber klassischem DNS: Das Gerät kann sich ausweisen. AdGuard Home nennt das Client-ID. Bei DoH steht sie im Pfad, etwa https://dns.sedv-lab.de/dns-query/iphone. Bei DoT und DoQ steht sie im Servernamen, den das Gerät beim TLS-Aufbau schickt, etwa iphone.dns.sedv-lab.de. Trägt man die Client-IDs in die Liste der zugelassenen Clients ein, beantwortet AdGuard Home nur noch diese Geräte. So wird aus dem DNS-Server kein offener Resolver, den jeder nutzen oder für Angriffe missbrauchen kann.
Für DoT und DoQ braucht der Server damit ein Zertifikat, das für jeden Namen unterhalb von dns.sedv-lab.de gilt, also ein Wildcard-Zertifikat für *.dns.sedv-lab.de. Das stellt Let's Encrypt nur über die DNS-Prüfung DNS-01 aus, bei der ein TXT-Eintrag in der Zone gesetzt wird. Caddy kann das nur mit einem Zusatzmodul. Wir nehmen deshalb lego, ein eigenständiges ACME-Programm mit Unterstützung für viele DNS-Anbieter.
Schritt 1: Feste DNS-Einträge anlegen
Im Beispiel zeigt bereits ein Wildcard-Eintrag * auf den Server, dns.sedv-lab.de und iphone.dns.sedv-lab.de lösen also schon auf. Darauf sollten Sie sich hier nicht verlassen. Legen Sie in der Zone feste Einträge an:
dns A 203.0.113.10
dns AAAA 2001:db8:1:2::10
*.dns A 203.0.113.10
*.dns AAAA 2001:db8:1:2::10
Der Grund zeigte sich im Test. Wir hatten lego zuerst ohne diese Einträge gestartet und dabei alle 20 Sekunden beim Nameserver von netcup nachgefragt. Sobald der TXT-Eintrag _acme-challenge.dns.sedv-lab.de sichtbar war, lieferte der Nameserver für dns.sedv-lab.de und für jeden Namen darunter keine Adresse mehr. Das ist kein Fehler von netcup, sondern in RFC 4592 so geregelt: Ein Wildcard gilt nur für Namen, die in der Zone gar nicht vorkommen, und durch den TXT-Eintrag existiert dns plötzlich als Zwischenstufe. Für Namen darunter wie iphone.dns greift *.sedv-lab.de dann ebenfalls nicht mehr, dort müsste ein eigener Eintrag *.dns stehen. Im Test war der Name so rund 10 Minuten lang nicht auflösbar, und das würde sich bei jeder Erneuerung wiederholen. Mit festen Einträgen für dns und *.dns greift die Regel nach RFC 4592 nicht.
Verifizieren: dig +short iphone.dns.sedv-lab.de A @root-dns.netcup.net liefert die IP des Servers. Bei netcup dauerte es im Test 7 bis 9 Minuten, bis neue Einträge dort sichtbar waren.
Schritt 2: Wildcard-Zertifikat mit lego
Die Zugangsdaten für die DNS-API kommen in eine Datei, die nur root lesen darf:
mkdir -p /opt/adguardhome/lego && cd /opt/adguardhome
cat > lego/netcup.env <<'EOF'
NETCUP_CUSTOMER_NUMBER=12345
NETCUP_API_KEY=IHR-API-KEY
NETCUP_API_PASSWORD=IHR-API-PASSWORT
NETCUP_PROPAGATION_TIMEOUT=1800
NETCUP_POLLING_INTERVAL=30
EOF
chmod 600 lego/netcup.env
NETCUP_PROPAGATION_TIMEOUT ist wichtig. lego wartet standardmäßig höchstens 900 Sekunden darauf, dass der TXT-Eintrag bei den Nameservern ankommt. Im Test lagen zwischen dem Anlegen und der Sichtbarkeit beim Nameserver von netcup knapp 9 Minuten. Mit dem Standardwert wäre das knapp geworden, 1800 Sekunden geben Luft.
docker run --rm -v "$PWD/lego:/data" --env-file lego/netcup.env goacme/lego:v5.6.0 \
run --path /data --accept-tos --email admin@sedv-lab.de --dns netcup \
-d dns.sedv-lab.de -d '*.dns.sedv-lab.de'
lego meldet beim Start this implementation use the legacy API, please use 'netcupv1' instead for Netcup CloudDNS API. Für das klassische DNS im Kundenkontrollpanel ist der Anbieter netcup richtig, netcupv1 gilt nur für das neuere CloudDNS. Dann folgt alle 30 Sekunden waiting for record propagation, bis Let's Encrypt prüfen kann.
Verifizieren: Am Ende stehen Server responded with a certificate und vier Dateien in lego/certificates/, darunter dns.sedv-lab.de.crt und dns.sedv-lab.de.key. Im Test dauerte der ganze Lauf 11 Minuten.
Schritt 3: AdGuard Home einrichten
Statt des Einrichtungsassistenten schreiben wir die Konfiguration selbst nach conf/AdGuardHome.yaml. Fehlende Einträge setzt AdGuard Home auf Standardwerte:
http:
address: 127.0.0.1:3000
doh:
insecure_enabled: true
users:
- name: admin
password: BCRYPT-HASH
dns:
bind_hosts:
- 0.0.0.0
port: 5300
serve_plain_dns: false
upstream_dns:
- https://dns.quad9.net/dns-query
- tls://dns.digitale-gesellschaft.ch
bootstrap_dns:
- 9.9.9.9
- 149.112.112.112
allowed_clients:
- iphone
- heimtest
trusted_proxies:
- 127.0.0.0/8
- ::1/128
tls:
enabled: true
server_name: dns.sedv-lab.de
port_https: 0
port_dns_over_tls: 853
port_dns_over_quic: 853
certificate_path: /certs/certificates/dns.sedv-lab.de.crt
private_key_path: /certs/certificates/dns.sedv-lab.de.key
filters:
- enabled: true
url: https://adguardteam.github.io/HostlistsRegistry/assets/filter_1.txt
name: AdGuard DNS filter
id: 1
schema_version: 34
Den BCrypt-Hash für das Passwort der Oberfläche erzeugen Sie laut Wiki von AdGuard Home mit htpasswd -B -C 10 -n -b admin 'IHR-PASSWORT' aus dem Paket apache2-utils und übernehmen den Teil nach dem Doppelpunkt. Die Oberfläche lauscht nur auf 127.0.0.1:3000, Sie öffnen sie über einen SSH-Tunnel. insecure_enabled erlaubt DoH über einfaches HTTP, denn HTTPS übernimmt Caddy. serve_plain_dns: false schaltet unverschlüsseltes DNS ab, der Port 5300 bleibt dann ganz zu.
Die compose.yaml:
services:
adguardhome:
image: adguard/adguardhome:v0.107.80
container_name: adguardhome
restart: unless-stopped
network_mode: host
volumes:
- ./work:/opt/adguardhome/work
- ./conf:/opt/adguardhome/conf
- ./lego:/certs:ro
docker compose up -d
docker logs adguardhome 2>&1 | grep -E "listening|fatal"
ss -tulpn | grep AdGuardHome
Hier lag die zweite Falle. Mit 0.0.0.0 und "::" unter bind_hosts, wie man es für IPv4 und IPv6 erwarten würde, brach AdGuard Home ab:
[fatal] starting dns server: configuring listeners: listening on tls addr [::]:853: listen tcp [::]:853: bind: address already in use
Für 0.0.0.0 öffnet AdGuard Home unter Linux einen gemeinsamen Socket für IPv4 und IPv6, das Log zeigt dafür [::]:853. Der zweite Eintrag will denselben Port noch einmal belegen. Lassen Sie "::" weg.
Verifizieren: ss zeigt *:853 für TCP und UDP und 127.0.0.1:3000, aber nichts auf 5300. Im Log steht plain dns is disabled. In der Oberfläche unter Einstellungen, Verschlüsselung erscheint das Zertifikat als gültig:

Der Warnhinweis certificates has no IP addresses; DNS-over-TLS won't be advertised via DDR ist harmlos. DDR ist ein Verfahren, mit dem ein Gerät von einem klassischen DNS-Server erfährt, dass dieser auch verschlüsselt spricht. Dafür müsste die IP-Adresse im Zertifikat stehen. Let's Encrypt stellt solche Zertifikate nur mit sechs Tagen Laufzeit aus, für eingetragene Adressen wie hier spielt DDR keine Rolle.
Schritt 4: DoH über Caddy
Für DoH reicht Caddy nur die DoH-Pfade an AdGuard Home weiter, alles andere beantwortet es mit 404. So ist die Oberfläche auch versehentlich nicht von außen erreichbar:
dns.sedv-lab.de {
import sicher
@doh path /dns-query /dns-query/*
handle @doh {
reverse_proxy 127.0.0.1:3000
}
handle {
respond 404
}
}
Dank trusted_proxies sieht AdGuard Home trotzdem die echte Adresse des Geräts, die Caddy im Header mitschickt. Für dieses Zertifikat nutzt Caddy wie gewohnt die HTTP-Prüfung. Im Test schlug sie zuerst fehl mit no valid A records found for dns.sedv-lab.de, weil die neuen Einträge aus Schritt 1 noch nicht verteilt waren. Nach einem caddy reload einige Minuten später klappte es.
Verifizieren: curl -s -o /dev/null -w '%{http_code}\n' https://dns.sedv-lab.de/ liefert 404, die Abfrage unter /dns-query/heimtest liefert 200.
Schritt 5: Nur bekannte Geräte zulassen
Die Liste allowed_clients aus der Konfiguration erscheint in der Oberfläche unter Einstellungen, DNS-Einstellungen, Zugriffsrechte:

Getestet haben wir von einem Rechner im Heimnetz mit kdig:
kdig @dns.sedv-lab.de +tls +tls-hostname=heimtest.dns.sedv-lab.de +tls-sni=heimtest.dns.sedv-lab.de example.org A
kdig @dns.sedv-lab.de +quic +tls-hostname=heimtest.dns.sedv-lab.de +tls-sni=heimtest.dns.sedv-lab.de example.org A
kdig @dns.sedv-lab.de +https=/dns-query/heimtest +tls-hostname=dns.sedv-lab.de example.org A
| Anfrage | Ergebnis im Test |
|---|---|
| DoT, DoQ und DoH mit zugelassener Client-ID | status: NOERROR mit Antwort |
DoT ohne Client-ID (dns.sedv-lab.de) | status: REFUSED |
| DoT und DoH mit unbekannter Client-ID | status: REFUSED, bei DoH mit HTTP-Status 200 |
Werbedomains doubleclick.net, googleadservices.com | Antwort 0.0.0.0 aus der Sperrliste |
| Klassisches DNS auf Port 53 oder 5300 von außen | keine Verbindung |
Solange einfaches DNS noch an war, bekamen fremde Absender über UDP auf Port 5300 überhaupt keine Antwort und über TCP REFUSED. AdGuard Home verwirft unbekannte UDP-Anfragen also, statt zu antworten. Das ist wichtig, denn genau diese Antworten nutzen Angreifer für Verstärkungsangriffe. Mit serve_plain_dns: false ist der Port zusätzlich zu.
Verifizieren: Im Abfrageprotokoll der Oberfläche steht bei den Anfragen die Client-ID.
Schritt 7: Zertifikat erneuern
Das Zertifikat gilt 90 Tage, lego erneuert es laut eigener Meldung frühestens nach rund 60 Tagen. Ein kleines Skript ruft lego auf und startet AdGuard Home neu, wenn sich das Zertifikat geändert hat:
#!/bin/sh
cd /opt/adguardhome
C=lego/certificates/dns.sedv-lab.de.crt
vorher=$(sha256sum "$C" | cut -d' ' -f1)
docker run --rm -v "$PWD/lego:/data" --env-file lego/netcup.env goacme/lego:v5.6.0 \
run --path /data --accept-tos --email admin@sedv-lab.de --dns netcup \
-d dns.sedv-lab.de -d '*.dns.sedv-lab.de' --no-random-sleep
nachher=$(sha256sum "$C" | cut -d' ' -f1)
[ "$vorher" != "$nachher" ] && docker restart adguardhome
Gespeichert als /opt/adguardhome/erneuern.sh mit chmod 700, dazu ein Eintrag in /etc/cron.d/adguard-zertifikat:
17 4 * * * root /opt/adguardhome/erneuern.sh >> /var/log/adguard-zertifikat.log 2>&1
Verifizieren: Ein Aufruf von Hand endet nach einer Sekunde mit Skip renewal: The certificate expires at …, solange keine Erneuerung fällig ist.
Was kostet der Umweg?
Wir haben vom Rechner im Heimnetz aus je 20 Abfragen gestellt, jede mit einer neuen Verbindung samt Verschlüsselung. Im Median brauchten DoT und DoH zum eigenen Server 63 Millisekunden, DoQ 69. DoT zu Quad9 lag mit 65 Millisekunden gleichauf. Klassisches DNS über UDP zu Quad9 war mit 21 Millisekunden schneller, weil der Verbindungsaufbau wegfällt. Der Ping zum VPS lag bei 16 Millisekunden. Im Alltag halten Telefone und Browser die verschlüsselte Verbindung offen, dann fällt der Aufbau nur einmal an.
Typische Fehler
| Meldung oder Beobachtung | Ursache und Lösung |
|---|---|
listen tcp [::]:853: bind: address already in use | bind_hosts enthält 0.0.0.0 und "::". Nur 0.0.0.0 eintragen, Schritt 3. |
| Name des DNS-Servers löst während der Zertifikatsprüfung nicht auf | Nur ein Wildcard-Eintrag in der Zone, der TXT-Eintrag hebelt ihn aus. Feste Einträge für dns und *.dns, Schritt 1. |
| lego bricht mit Zeitüberschreitung bei der Propagation ab | Der Nameserver braucht länger als NETCUP_PROPAGATION_TIMEOUT. Wert auf 1800 setzen. |
Caddy meldet no valid A records found for dns.sedv-lab.de | Neue Einträge noch nicht verteilt. Einige Minuten warten, dann caddy reload. |
status: REFUSED für das eigene Gerät | Client-ID fehlt oder steht nicht in allowed_clients. Bei DoT den Namen ID.dns.sedv-lab.de verwenden, bei DoH den Pfad /dns-query/ID. |
DNS-over-TLS won't be advertised via DDR | Kein Fehler. Zertifikat ohne IP-Adresse, DDR fällt weg. |
Häufige Fragen
Warum nicht einfach Port 53 öffnen?
Ein offener Resolver auf Port 53 wird schnell gefunden und für Verstärkungsangriffe missbraucht. Manche Netze unterwegs blockieren Port 53 nach außen, und klassisches DNS kann jeder auf dem Weg mitlesen. DoT, DoH und DoQ sind verschlüsselt, und über die Client-ID beantwortet der Server nur Ihre Geräte.
Brauche ich alle drei Protokolle?
Nein. Apple-Geräte nutzen über Profile DoH oder DoT, Browser meist DoH. DoQ ist das jüngste Verfahren und wird noch von wenigen Programmen unterstützt. Alle drei kosten in AdGuard Home nur einen Port in der Konfiguration.
Sieht der VPS-Anbieter meine Anfragen?
Die Anfragen kommen verschlüsselt beim VPS an. AdGuard Home fragt die Upstream-Server im Beispiel ebenfalls verschlüsselt über DoH und DoT an. Das Abfrageprotokoll liegt aber auf dem VPS. Wer das nicht möchte, schaltet es in den Einstellungen ab oder kürzt die Aufbewahrung.
Geht das auch ohne Wildcard-Zertifikat?
Für DoH ja, denn dort steht die Client-ID im Pfad, und ein normales Zertifikat von Caddy genügt. Für DoT und DoQ ohne Wildcard müsste jede Client-ID als eigener Name ins Zertifikat, und bei jedem neuen Gerät wäre ein neues Zertifikat fällig.
Fazit
Ein eigener verschlüsselter DNS-Server auf dem VPS ist in einer Stunde eingerichtet und war im Test so schnell wie DoT bei Quad9. Die Arbeit steckt im Zertifikat: Für DoT und DoQ mit Client-ID braucht es ein Wildcard-Zertifikat über DNS-01, und dafür feste Einträge in der Zone und Geduld mit dem Nameserver. Mit der Liste zugelassener Clients und abgeschaltetem einfachen DNS bleibt der Server Ihr eigener und wird nicht zum offenen Resolver.
Weiterführende Anleitungen und Quellen
- Teil 3: Domain fürs Lab verdrahten
- Teil 4: Caddy als Eingangstür
- Teil 8: HomeLab ohne Portfreigabe
- Docker und Docker Compose auf Linux installieren
- Docker Compose absichern
- AdGuard Home: Konfiguration
- AdGuard Home: Clients und Client-IDs
- AdGuard Home v0.107.80
- lego: DNS-Anbieter netcup
- RFC 4592: Wildcards im DNS


