{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreidpc64fgtdnrc2lfyoyy7nigjulgroi6dcw4xcuc4aud2nquyqrxe",
    "uri": "at://did:plc:nkxz2ojdvmieg2nhphvputvp/app.bsky.feed.post/3mmj6k76mdig2"
  },
  "coverImage": {
    "$type": "blob",
    "ref": {
      "$link": "bafkreibp7mlw67xeilfpna3i6v2zw65gfh54rxouqo4sqghb5gytmgeqri"
    },
    "mimeType": "image/png",
    "size": 1172664
  },
  "description": "Wofür braucht man noch gleich DoT oder DoH?\n\nNun, wenn du eine Internetadresse eingibst, muss dein Gerät zuerst herausfinden, zu welchem Server diese Adresse gehört. Diese Nachfragen heißen DNS. Lange Zeit liefen sie unverschlüsselt durchs Netz, vergleichbar mit einer Postkarte. Jeder, der den Datenverkehr sehen konnte, wusste dadurch sehr genau, welche Webseiten aufgerufen werden, und konnte die Antworten sogar manipulieren.\n\nDoT und DoH lösen genau dieses Problem. Beide sorgen dafür, […]",
  "path": "/2026/01/03/bind-9-20-auf-freebsd-15-dns-over-tls-dot-und-dns-over-https-doh-sicher-konfigurieren/",
  "publishedAt": "2026-05-23T09:53:53.000Z",
  "site": "https://www.kernel-error.de",
  "tags": [
    "Bind",
    "DNS",
    "FreeBSD",
    "ITSecurity",
    "ns1KernelErrorDe",
    "DoT",
    "DoH",
    "dns.kernel-error.de",
    "DNS-Amplification",
    "bind9.readthedocs.io+1",
    "RFC 8484",
    "GitHub-curl-wiki",
    "China",
    "hier",
    "HTTPS RR und SVCB Records",
    "@37.120.183.220",
    "@2a03",
    "@93.177.67.26",
    "@-"
  ],
  "textContent": "### Wofür braucht man noch gleich DoT oder DoH?\n\nNun, wenn du eine Internetadresse eingibst, muss dein Gerät zuerst herausfinden, zu welchem Server diese Adresse gehört. Diese Nachfragen heißen DNS. Lange Zeit liefen sie unverschlüsselt durchs Netz, vergleichbar mit einer Postkarte. Jeder, der den Datenverkehr sehen konnte, wusste dadurch sehr genau, welche Webseiten aufgerufen werden, und konnte die Antworten sogar manipulieren.\n\nDoT und DoH lösen genau dieses Problem. Beide sorgen dafür, dass diese DNS-Nachfragen verschlüsselt übertragen werden. Bei DNS over TLS, kurz DoT, wird die Anfrage in eine eigene sichere Verbindung gepackt. Außenstehende sehen noch, dass eine DNS-Anfrage stattfindet, aber nicht mehr, welche Webseite gemeint ist. Bei DNS over HTTPS, kurz DoH, wird dieselbe Anfrage zusätzlich im normalen Webseitenverkehr versteckt. Von außen sieht sie aus wie ein ganz gewöhnlicher Zugriff auf eine Website.\n\nDer Zweck von beiden ist also derselbe: Schutz der Privatsphäre und Schutz vor Manipulation. Der Unterschied liegt darin, wie sichtbar diese Nachfragen noch sind. DoT ist transparent und gut kontrollierbar, DoH ist unauffälliger, kann dafür aber lokale Regeln und Schutzmechanismen umgehen.\n\nMal angenommen, du möchtest eine gewisse Webseite aufrufen. Dann geht der Client los und holt über einen DNS-Server die IP-Adressen vom Server. Dies kann man mitlesen und ggf. verändern. Mitlesen sagt dem Mitlesenden, wo du dich so im Internet herumtreibst. Verändern könnte man als Angriff nutzen, indem man dir einfach eine andere Webseite vorsetzt, während du versuchst, dich in deinen Mailaccount einzuloggen. Beides wird durch DoH und DoT deutlich erschwert.\n\nDann soll es ja Netzwerke geben, in welchen dir ein bestimmter DNS-Server aufgezwungen wird, weil dieser DNS-Server nach Werbung oder ungewollten Inhalten filtert. Damit dies nun ebenfalls nicht einfach umgangen werden kann, blockt man den Zugriff aus dem Netzwerk einfach auf die Ports, welche sonst für eine DNS-Abfrage benutzt werden (TCP/53, UDP/53, TCP/853). Da kommt nun DoH ins Spiel, denn das läuft auf dem ganz normalen HTTPS-Port TCP/443. Blockt man den, kann keiner mehr auf Webseiten zugreifen (ok, unverschlüsselt, aber hey, das macht doch keiner mehr, oder?).\n\nDie Zeit ging weiter – BIND auch.\nMeine älteren Artikel zu DoT/DoH waren für ihren Zeitpunkt korrekt, aber inzwischen hat sich an zwei Stellen richtig was getan:\n\n  1. **BIND spricht DoT/DoH nativ** (kein Stunnel-/Proxy-Zirkus mehr nötig – außer du willst bewusst terminieren/filtern).\n  2. **„Authoritative + Public Resolver auf derselben Kiste“** ist ohne klare Trennung schnell ein Sicherheitsproblem (Open-Resolver/Reflection-Missbrauch lässt grüßen).\n\n\n\nDarum gibt’s hier das Update:\n\n  * **ns1.kernel-error.de** : nur autoritativ auf **UDP/TCP 53** (Zonen, DNSSEC wie gehabt)\n  * **dns.kernel-error.de** : Public Resolver nur auf **DoT 853/TCP** und **DoH 443/TCP** (rekursiv, DNSSEC-validierend)\n  * **Trennung über zusätzliche IPs + Views**. Ergebnis: Authoritative bleibt „stumm rekursiv“, Resolver ist nur über TLS/HTTPS erreichbar.\n\n\n\n### Zielbild\n\nUff, ich muss zugeben, diesen Beitrag schon VIEL zu lange als Draft zu haben. Es ist einfach viel zu schreiben, bschreiben und mir fehlte die Zeit. Aber das kennt ihr ja. OK… das Zielbild, was soll es werden?\n\n**Was soll am Ende gelten:**\n\n  * Port 53 auf Authoritative-IP(s):\n    * beantwortet **nur** meine autoritativen Zonen\n    * **keine Rekursion** → REFUSED bei `google.com`\n  * DoT/DoH auf separaten Resolver-IP(s):\n    * rekursiv für „das ganze Internet“\n    * DNSSEC-Validation aktiv\n    *  _kein_ offenes UDP/53 → weniger Angriffsfläche für Reflection/Amplification\n\n\n\n**Warum das wichtig ist:**\nEin „Public Resolver“ ist per Definition attraktiv für Missbrauch. Der Klassiker ist **DNS-Amplification** über UDP/53. Wenn man Rekursion auf 53 offen hat, ist man sehr schnell Teil fremder Probleme. DoT/DoH sind TCP-basiert – das ist schon mal deutlich unattraktiver für Reflection. (Nicht „unmöglich“, aber praktisch viel weniger lohnend.)\n\n### Warum „Views“ – und warum zusätzliche IPs?\n\n### 1) Views – weil Policy pro Anfrage gelten muss\n\nWir wollen auf _derselben named-Instanz_ zwei sehr unterschiedliche Rollen:\n\n  * **Authoritative** : `recursion no;`\n  * **Resolver** : `recursion yes;` + Root-Hints/Cache\n\n\n\nDas muss **pro eingehender Anfrage** entschieden werden. Dafür sind Views da.\n\n2) Also: Trennung über Ziel-IP (match-destinations)\n\nWenn wir **DoH/DoT auf andere IPs legen** , kann die View anhand der Zieladresse entscheiden:\n\n  * Anfrage geht an `93.177.67.26` / `2a03:4000:38:20e::53` → **auth-View**\n  * Anfrage geht an `37.120.183.220` / `2a03:4000:38:20e::853` → **resolver-View**\n\n\n\nUnd genau deshalb brauchen wir:\n\n  * **zusätzliche IPs** (damit die Rollen sauber getrennt sind)\n  * **separaten FQDN** `dns.kernel-error.de` (damit Clients überhaupt sinnvoll DoT/DoH nutzen können – und für TLS/SNI/Cert-Match)\n\n\n\nWenn du also grade ein ripe from ausfüllst und angeben musst, warum da eine weitere IPv4 Adresse „verbrannt“ werden soll, hast du nun eine gute Antwort.\n\nBIND-Config\n\nIch beschreibe hier nur die Teile, die für das Rollen-Split relevant sind. Die Zonendateien/Slaves bleiben wie sie sind.\n\n### 1) /usr/local/etc/namedb/named.conf – Views\n\n**Wichtig:** Sobald wir `view {}` nutzen, müssen _alle_ Zonen in Views liegen, sonst bricht `named-checkconf` ab. Das ist kein „Feature“, das ist BIND. Leicht nervig, vor allem wenn man nun viel in seinem Setup umschreiben muss. Aber ich eigentlich schon mal erwähnt, dass ich auf der Arbeit mal einen, nennen wir es mal View Ersatz, für powerdns gesehen habe? Da hat tatsächlich jemand mit einer Cisco ASA in die DNS Pakete geschaut und je nachdem welche quelle angefragt hat, wurde dann durch die ASA eine neue Adresse in die DNS Pakete geschrieben. Furchtbar! Richtig schlimm. Bis man so etwas findet, wenn man es nicht weiß. DNSsec geht kaputt und aaahhhhhhaaaaaahhhhh. Egal, mein PTBS kickt da grade. Öhm wo waren wir? Genau…\n\nBeispiel:\n\n\n    include \"/usr/local/etc/namedb/named.conf.options\";\n\n    view \"auth\" {\n        match-clients { any; };\n        match-destinations { 93.177.67.26; 2a03:4000:38:20e::53; };\n\n        recursion no;\n        allow-recursion { none; };\n        allow-query-cache { none; };\n        allow-query { any; };\n\n        include \"/usr/local/etc/namedb/named.conf.default-zones\";\n        include \"/usr/local/etc/namedb/named.conf.master\";\n        include \"/usr/local/etc/namedb/named.conf.slave\";\n    };\n\n    view \"resolver\" {\n        match-clients { any; };\n        match-destinations { 37.120.183.220; 2a03:4000:38:20e::853; 127.0.0.1; ::1; };\n\n        recursion yes;\n        allow-recursion { any; };\n        allow-query-cache { any; };\n        allow-query { any; };\n\n        zone \".\" { type hint; file \"/usr/local/etc/namedb/named.root\"; };\n    };\n\n**Warum Root-Hints nur im Resolver-View?**\nWeil nur dieser View rekursiv arbeiten soll. Ohne Root-Hints ist Rekursion tot; dat wolln wa so!\n\n### 2) /usr/local/etc/namedb/named.conf.options – Listener-Trennung + DoH/DoT\n\nDer „Aha-Moment“ hier: Wir trennen nicht nur per View, sondern auch per `listen-on`.\nDamit bindet named die Ports wirklich nur auf den gewünschten IPs.\n\n**Authoritative (nur 53):**\n\n\n    listen-on { 93.177.67.26; 127.0.0.1; };\n    listen-on-v6 { 2a03:4000:38:20e::53; ::1; };\n\n**DoT auf Resolver-IPs (+ Loopback für lokale Tests):**\n\n\n    listen-on port 853 tls local-tls { 37.120.183.220; 127.0.0.1; };\n    listen-on-v6 port 853 tls local-tls { 2a03:4000:38:20e::853; ::1; };\n\n**DoH auf Resolver-IPs (+ Loopback):**\nBIND 9.18+ kann DoH nativ, Endpoint typischerweise `/dns-query`\n\n\n    http doh-local {\n        endpoints { \"/dns-query\"; };\n        listener-clients 1000;\n        streams-per-connection 256;\n    };\n\n    listen-on port 443 tls local-tls http doh-local { 37.120.183.220; 127.0.0.1; };\n    listen-on-v6 port 443 tls local-tls http doh-local { 2a03:4000:38:20e::853; ::1; };\n\n**TLS-Block (DoT/DoH):**\n\n\n    tls local-tls {\n        cert-file \"/usr/local/etc/nginx/ssl/wild.kernel-error.de/2025/ecp/chain.crt\";\n        key-file \"/usr/local/etc/nginx/ssl/wild.kernel-error.de/2025/ecp/http.key\";\n        protocols { TLSv1.2; TLSv1.3; };\n        ciphers \"ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256\";\n        cipher-suites \"TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256\";\n        prefer-server-ciphers yes;\n        session-tickets no;\n    };\n\n**„Ich schalte nginx davor – muss BIND TLS können?“**\nWenn nginx wirklich TLS terminiert, _kann_ BIND auch ohne TLS dahinter laufen – dann sprichst du intern HTTP/2 cleartext oder HTTP/1.1, je nach Setup. Das habe ich ebenfalls so umgesetzt, es hängt immer etwas davon ab, was man so will und wie groß das Setup wird. Ich lasse es in diesem Beitrag aber mal weg, so läuft alles nur mit bind. Ob BIND dafür „tls none“/HTTP-Listener sauber unterstützt, hängt an der BIND-DoH-Implementierung – hier ist die BIND/ARM-Doku die Wahrheit. bind9.readthedocs.io+1\n\n\n### Testplan – Linux-CLI – **bewusst IPv4 und IPv6**\n\nWir wollen natürlich einmal reproduzierbar testen. Also: jede Stufe zweimal. Einmal `-4`, einmal `-6`. Also ob es bei IPv4 und bei IPv6 jeweils korrekt ist. Ihr könnt euch nicht vorstellen, wie oft ich fest davon überzeugt bin, es für beide Adressfamilien korrekt konfiguriert zu haben, dann aber noch ein unterschied zwischen v4 und v6 ist. Daher testen wir das.\n\n### Voraussetzungen auf Linux\n\n\n    which dig kdig curl openssl\n\n### Schritt 1 – DoT-TLS-Handshake prüfen (IPv4/IPv6)\n\n### IPv4\n\n\n    openssl s_client \\\n      -connect 37.120.183.220:853 \\\n      -servername dns.kernel-error.de \\\n      -alpn dot\n\nErwartung:\n\n  * Zertifikat passt auf `dns.kernel-error.de` (SAN / Wildcard ok)\n  * `ALPN protocol: dot`\n  * `Verify return code: 0 (ok)`\n\n\n\n### IPv6\n\n\n    openssl s_client \\\n      -connect '[2a03:4000:38:20e::853]:853' \\\n      -servername dns.kernel-error.de \\\n      -alpn dot\n\nWenn das passt, ist TLS-Transport ok. Also nur die TLS Terminierung für IPv4 und IPv6, da war noch keine DNS Abfrage enthalten.\n\n### Schritt 2 – DoT-Query (kdig) – IPv4/IPv6\n\n### IPv4\n\n\n    kdig +tls @37.120.183.220 google.com A\n\nErwartung:\n\n  * `status: NOERROR`\n  * Flags: `rd ra` (Recursion Desired/Available)\n  * eine A-Antwort\n\n\n\n### IPv6\n\n\n    kdig +tls @[2a03:4000:38:20e::853] google.com A\n\nGleiche Erwartungshaltung wie bei IPv4.\n\n### Schritt 3 – Sicherstellen: **kein** Resolver auf UDP/TCP 53\n\n### Resolver-IPs dürfen auf 53 _nicht_ antworten\n\n\n    dig -4 @37.120.183.220 google.com A\n    dig -6 @2a03:4000:38:20e::853 google.com A\n\nErwartung:\n\n  * Timeout / no servers reached\nGenau das wollen wir ja: kein UDP/53 auf den Resolver-IPs.\n\n\n\n### Authoritative-IPs dürfen nicht rekursiv sein\n\n\n    dig -4 @93.177.67.26 google.com A\n    dig -6 @2a03:4000:38:20e::53 google.com A\n\nErwartung:\n\n  * `status: REFUSED`\n  * idealerweise `EDE: (recursion disabled)`\nDas ist genau die „nicht missbrauchbar als Open-Resolver“-Bremse.\n\n\n\nUnd unser positiver Check:\n\n\n    dig -4 @93.177.67.26 kernel-error.de A\n    dig -6 @2a03:4000:38:20e::53 kernel-error.de A\n\nErwartung:\n\n  * `aa` gesetzt (authoritative answer)\n  * Antwort aus meiner Zone\n\n\n\n### Schritt 4 – DoH GET (Base64url) – IPv4/IPv6\n\n### 4.1 Query bauen (DNS-Wireformat → base64url)\n\nBeispiel `google.com A`:\n\n\n    echo -n -e '\\x12\\x34\\x01\\x00\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x06google\\x03com\\x00\\x00\\x01\\x00\\x01' \\\n    | base64 -w0 | tr '+/' '-_' | tr -d '='\n\nDas Ergebnis ist mein `dns=` Parameter (base64url ohne `=` padding). Das ist DoH-Standard nach RFC 8484.\n\n4.2 DoH GET erzwingen – IPv4\n\n\n    curl -4 --http2 -s \\\n    'https://dns.kernel-error.de/dns-query?dns=<DEIN_DNS_PARAM>' \\\n    | hexdump -C\n\nIPv6\n\n\n    curl -6 --http2 -s \\\n    'https://dns.kernel-error.de/dns-query?dns=<DEIN_DNS_PARAM>' \\\n    | hexdump -C\n\nErwartung:\n\n  * HTTP/2 200\n  * `content-type: application/dns-message`\n  * Im Hexdump siehst du eine valide DNS-Response.\n\n\n\n### Schritt 5 – DoH POST (application/dns-message) – IPv4/IPv6\n\nDas ist der „richtige“ DoH-Weg für Tools/Clients.\n\n### IPv4\n\n\n    printf '\\x12\\x34\\x01\\x00\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x06google\\x03com\\x00\\x00\\x01\\x00\\x01' \\\n    | curl -4 --http2 -s \\\n      -H 'content-type: application/dns-message' \\\n      --data-binary @- \\\n      https://dns.kernel-error.de/dns-query \\\n    | hexdump -C\n\n### IPv6\n\n\n    printf '\\x12\\x34\\x01\\x00\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x06google\\x03com\\x00\\x00\\x01\\x00\\x01' \\\n    | curl -6 --http2 -s \\\n      -H 'content-type: application/dns-message' \\\n      --data-binary @- \\\n      https://dns.kernel-error.de/dns-query \\\n    | hexdump -C\n\nErwartung:\n\n  * DNS-Response im Wireformat\n  * keine HTML-Antwort, kein Redirect-Quatsch\n\n\n\n### Was wir damit jetzt sicher(er) gelöst haben:\n\n  * **Kein Open-Resolver auf UDP/53** → massiver Gewinn gegen DNS-Amplification.\n  * **Authoritative bleibt Authoritative** → Zonen-Betrieb unverändert stabil.\n  * **Resolver nur über DoT/DoH** → TCP/TLS-Transport, weniger Missbrauchsfläche.\n  * **Saubere technische Trennung** → Views per Ziel-IP sind simpel, robust, nachvollziehbar.\n\n\n\nUnd ja: „Public Resolver“ heißt trotzdem Monitoring/Rate-Limiting/Abuse-Handling.\nDas Feintuning (RRL, QPS-Limits, minimal-responses, Response-Policy, ggf. ECS-Handling, Logging, Fail2ban-Signale) ist das nächste Kapitel. Wobei, wenn ich grade auf die TLS Parameter schaue, sollte ich da vielleicht noch mal nacharbeiten, hm?\n\nWenn ihr noch eine kleine liste von erreichbaren Servern sucht: GitHub-curl-wiki\n\nAlles hilft natürlich nicht, wenn man am Ende doch komplett IP- oder Hostnamebasiert geblockt wird. In China ist da nicht viel zu holen und auch hier gibt es immer mal wieder etwas.\n\n* * *\n\nJapp… TLS geht besser. Im Beitrag habe ich es oben schon angepasst, es war:\n\n\n    tls local-tls {\n        cert-file \"/pfad/chain.crt\";\n        key-file  \"/pfad/http.key\";\n        dhparam-file \"/pfad/dhparam.pem\";\n        protocols { TLSv1.2; TLSv1.3; };\n        ciphers \"TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256\";\n        prefer-server-ciphers yes;\n        session-tickets no;\n    };\n\n  * **dhparam-file** ist komplett raus weil, ja weil es nicht benutzt wird ich mach ja kein DHE sondern ECDHE\n  * cipher-suites für TLS1.3 waren nicht gesetzt.\n  * Dann konnten auch gleich die Cipher aufgeräumt werden.\n\n\n\nHey, da hat es sich doch gelohnt, das mal runter zu schreiben. So habe ich es direkt gefunden und nicht erst, weil mich jemand von euch darauf hinweist (macht das aber bitte immer wenn ich hier Mist schreibe) oder es beim nächsten eigenen Audit auffällt.\n\n**Siehe auch:** HTTPS RR und SVCB Records — die passenden DNS-Records, damit Clients dieses DoH/DoT-Setup automatisch entdecken können (RFC 9461).",
  "title": "BIND auf FreeBSD: DoT & DoH einrichten mit Views, IP‑Trennung und Testplan für IPv4/IPv6."
}