Files
jarl/docs/encrypted-dns.md
T
adamandClaude Opus 5.5 13ffd8cad4 Encrypted DNS health, comparing a zone's views, --local everywhere
jarld checks the DoT and DoH listeners every 10 minutes as a client would (page, dashboard, jarl encrypted-dns health, jarl_encrypted_dns_up). The Compare views tab and jarl zone diff show what differs between a zone's views, with Use this version. --local works on unlinked zones. A changed certificates.dir is followed by encrypted DNS's tls statements. Sharing an already shared version replaces other views' overrides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 20:46:56 +01:00

7.9 KiB

DNS over TLS and HTTPS

named (BIND 9.20) can answer over encrypted connections itself: DNS over TLS (DoT, port 853) and DNS over HTTPS (DoH, at /dns-query). Your phones, laptops and browsers then ask this server without anyone on the network seeing or changing their queries, and they get exactly what plain DNS gives them: the same views, the same records and the same blocking.

Jarl sets it up in one step: a certificate for the host name clients connect to (obtained and renewed by Jarl, certificates.md), named's tls statement and listeners on the addresses you choose, and the host name's address records in your private views.

In the web UI: Encrypted DNS (administrators). In the CLI: jarl encrypted-dns. In the API: /api/v1/encrypted-dns.

Setting it up

jarl encrypted-dns setup --hostname dns.adbraun.lol --address 10.0.10.5 --address 2a02:8013:7f3d:10::5

This:

  1. finds or obtains the certificate: one Jarl manages whose names cover the host name (--certificate NAME to pick one), or a new one named after the host name's first label, which Jarl obtains from the CA (the command waits). The certificate gets the dns use, so it is written where named can read it, and named rereads it on every renewal.

  2. checks the ports: nothing else may listen on the chosen addresses on port 853 or the DoH port. Jarl looks at the host's listening sockets (it binds nothing) and says what is in the way, e.g. jarld (Jarl's web UI and API) listens on 10.0.10.5 port 443, and which port is free instead (--doh-port 8443).

  3. stages named's configuration, as one change you can review:

    tls jarl-dns { cert-file ".../tls/dns/fullchain.pem"; key-file ".../tls/dns/privkey.pem"; protocols { TLSv1.2; TLSv1.3; }; };
    options {
        listen-on port 853 tls jarl-dns { 10.0.10.5; };
        listen-on port 443 tls jarl-dns http default { 10.0.10.5; };
        listen-on-v6 port 853 tls jarl-dns { 2a02:8013:7f3d:10::5; };
        ...
    

    named listens on port 53 on every address only while there is no listen-on statement at all. The first one, for DoT, would end that, so when you have none the setup adds listen-on { any; }; (and the same for IPv6) to keep plain DNS where it was.

  4. adds the host name's A and AAAA records with the chosen addresses, in the private views that have a zone for it (--view to choose). In a linked zone they are local to the view, so the name doesn't reach your external view.

Running it again replaces the earlier setup (other addresses, ports, DoT or DoH only with --no-doh / --no-dot). jarl encrypted-dns disable takes the listeners and the tls statement out; the certificate and the records stay.

Option Default
--hostname The name clients connect to; the certificate is for it
--address This server's addresses to listen on (repeat). Choose your own networks' addresses: a listener on a public address answers anyone who reaches it, like port 53 there
--certificate one for the host name, obtained if needed A certificate Jarl manages
--no-dot, --no-doh both on
--dot-port 853 Clients expect 853
--doh-port 443 Any free port; clients take it in the URL
--view private views with the zone Where the host name's records go

In the web UI the same form checks the ports as you choose, offers the free port, and obtains the certificate before setting up.

Which view clients get

A DoT or DoH client gets the view its address matches, as over plain DNS: your phone at home gets the internal view, with its records and blocking. Choosing a view by key (match-clients { key ...; }) doesn't work over DoT and DoH, as clients don't sign their queries.

Clients

jarl encrypted-dns (and the page) shows what to give them:

For DoT clients:   dns.adbraun.lol
For DoH clients:   https://dns.adbraun.lol/dns-query
Client Where
Android 9+ Settings → Network → Private DNS → Private DNS provider hostname: dns.adbraun.lol (DoT)
Linux (systemd-resolved) /etc/systemd/resolved.conf: DNS=10.0.10.5#dns.adbraun.lol and DNSOverTLS=yes (or opportunistic, which falls back to plain DNS)
Firefox Settings → Privacy & Security → DNS over HTTPS → Increased protection → Custom: the DoH URL
Chrome, Edge Settings → Privacy and security → Security → Use secure DNS → custom: the DoH URL
Windows 11 Settings → Network → your connection → DNS server assignment → Edit: the server's address, DNS over HTTPS on (manual template), the DoH URL

Test from a machine on your network:

kdig +tls-ca +tls-host=dns.adbraun.lol @10.0.10.5 example.com
dig +https @10.0.10.5 example.com
curl -s -H 'accept: application/dns-json' 'https://dns.adbraun.lol/dns-query?name=example.com'

Android away from home. Private DNS in hostname mode applies on every network. Away from home, your server can't be reached, and Android then resolves nothing at all ("Private DNS server cannot be accessed"). Use a VPN home (WireGuard) when you're out, or switch Private DNS to Automatic when you leave. Firefox's Increased protection and systemd-resolved's opportunistic fall back to normal DNS by themselves.

Clients check the certificate against the host name, so the name must resolve to this server for them: the setup adds it to your private views. Open the ports in the server's firewall for your own networks only.

Health

jarld checks the listeners itself, a minute after it starts and then every 10 minutes, the way a client would: it connects to each, checks the certificate named serves (for the host name, current, and the one Jarl last wrote, so a renewal named hasn't reread shows up) and asks for the host name. The Encrypted DNS page shows the result per listener (Check now checks again), the dashboard a card and the problems, Prometheus jarl_encrypted_dns_up{kind,address,port}:

jarl encrypted-dns health        # check now; exit 1 when a listener doesn't work

What it can say:

nothing listens on ADDR port P named couldn't bind the port: look in named's log (ports below 1024 need named to start as root, as the Debian service does)
no answer within 5s a firewall on the server
a certificate for X, not HOST named serves another certificate
an older certificate named hasn't reread the renewed files (rndc reconfig)
not from a CA this server trusts (warning) a staging or test CA: clients reject it unless they trust it
REFUSED (warning) it works, but this server's own address gets a view that doesn't answer for the host name

What Jarl checks

  • A tls statement whose files don't exist is an error (tls_file_missing): named won't load its configuration without them.
  • A certificate named uses can't be deleted, nor lose its dns use: turn encrypted DNS off first.
  • Listeners you wrote yourself are left alone; the page notes them.

API

Method Path Body
GET /encrypted-dns enabled, certificate, hostname, dot and doh (address and port), dot_host, doh_url, listeners; for the form: addresses (this server's, with private), views (with public, and has_zone for ?hostname=), certificates
POST /encrypted-dns/check {"addresses", "dot", "doh", "dot_port", "doh_port"}: conflicts (address, port, what) and free_doh_port
POST /encrypted-dns {"hostname", "certificate", "addresses", "dot", "doh", "dot_port", "doh_port", "views"}: staged or applied like any change; 409 while the certificate hasn't been obtained
DELETE /encrypted-dns Turns it off
POST /encrypted-dns/health Checks the listeners now: checked, probes (kind, address, port, ok, problem, warning, certificate names, expiry and serial, trusted, rcode, ms). GET /encrypted-dns has the last check as health