DNS Over HTTPS at Home: Running Your Own DoH Resolver

Stop leaking every domain you visit to whoever runs your router's resolver

Plain DNS is the last great open postcard of the internet. You’ve encrypted your web traffic with TLS, you’ve got a VPN you trust, you’ve hardened SSH to within an inch of its life — and then your machine cheerfully shouts every single domain name you look up, in clear text, over UDP port 53, to whatever resolver your ISP handed you at DHCP time. Anyone on the path can read it, and plenty of middleboxes do.

DNS over HTTPS (DoH) fixes the on-the-wire bit by wrapping your queries in ordinary HTTPS. The usual advice is “just point your browser at Cloudflare or Google”, which works, but it swaps one party who can see all your lookups (your ISP) for another (a giant ad-and-cloud company). The more interesting move is to run the DoH resolver yourself, so the encryption terminates on your hardware and you decide who, if anyone, your queries go to upstream.

Advertisement

There are two jobs here and it’s worth keeping them separate in your head:

  1. A recursive/forwarding resolver that does the actual DNS work and ideally adds ad-blocking. I use Unbound, often behind Pi-hole.
  2. A DoH front-end that accepts encrypted HTTPS queries from clients and hands the decrypted query to that resolver.

The tidiest tool for job two is cloudflared in proxy-dns mode, or dnscrypt-proxy. I’ll show cloudflared because it’s a single binary and configures in three lines.

First, a small Unbound config that listens only on localhost and does proper recursion with DNSSEC validation:

# /etc/unbound/unbound.conf.d/local.conf
server:
    interface: 127.0.0.1
    port: 5335
    do-ip6: no
    harden-dnssec-stripped: yes
    harden-glue: yes
    qname-minimisation: yes
    prefetch: yes
    cache-min-ttl: 300
    access-control: 127.0.0.0/8 allow

qname-minimisation is the privacy win here: Unbound only tells each authoritative server the part of the name it actually needs, instead of leaking the full hostname to the root and TLD servers.

Now the front-end. This makes cloudflared listen for DoH on a local port and forward the plaintext query to Unbound:

$ cloudflared proxy-dns \
    --address 127.0.0.1 \
    --port 5553 \
    --upstream https://127.0.0.1:5335 \
    --max-upstream-conns 5
INF Adding DNS upstream url=https://127.0.0.1:5335
INF Starting DNS over HTTPS proxy server address=https://127.0.0.1:5553/dns-query

That gives you a DoH server other machines can use. To expose it on your network with a real certificate, put a reverse proxy in front. A Caddy block does this in about as few lines as anything can:

doh.example.com {
    reverse_proxy /dns-query 127.0.0.1:5553
}

Caddy fetches and renews the Let’s Encrypt cert automatically, so clients see a clean https://doh.example.com/dns-query endpoint.

Firefox makes this trivial — Settings → Privacy → enable DNS over HTTPS → “Custom” → paste your URL. On a whole-OS basis, systemd-resolved (recent versions) and Android’s “Private DNS” can use it too, though Android wants DoT (port 853), not DoH, so keep that in mind.

To prove it works, query the endpoint by hand with curl using the DoH wire format:

$ curl -sH 'accept: application/dns-json' \
    'https://doh.example.com/dns-query?name=vo.rs&type=A' | jq .Answer
[
  {
    "name": "vo.rs",
    "type": 1,
    "TTL": 300,
    "data": "203.0.113.42"
  }
]

If that returns an answer, your resolver is decrypting, recursing, and replying — all on hardware you control.

Be clear-eyed about the threat model. DoH stops the network path from your devices to your resolver from snooping or tampering. It does not hide your lookups from your upstream — and if you forward to Cloudflare, you’re back to trusting Cloudflare. The genuinely private setup is Unbound doing full recursion (no forwarder), so your queries fan out across the authoritative hierarchy rather than passing through one company.

It also doesn’t hide which sites you visit from anyone watching your IP traffic, because TLS SNI and the destination IP still give the game away. DoH is one layer, not a cloak.

The other gotcha: when DNS encryption breaks, it fails confusingly. A wrong port or an expired cert manifests as “the whole internet is down”, and you’ll spend twenty minutes blaming everything but DNS, as is tradition.

Running your own DoH resolver is worth it if you genuinely care about who sees your lookups and you’re willing to own the failure modes. The Unbound-plus-cloudflared combo is maybe an hour of work and gives you encrypted, validating, ad-filtered DNS that terminates on your own box. If you just want browser-level encryption and don’t mind Cloudflare reading your queries, the built-in Firefox toggle is two clicks and you should stop reading. For everyone in between — homelabbers who want the real thing without a weekend project — this is the sweet spot.

Advertisement

Related Content

Advertisement
Smarc
Written by Smarc

Founder and editor of vo.rs. A lifelong tinkerer who self-hosts far more than is sensible, hardens Linux boxes for fun, and prods the latest AI tools to see what they can really do. The how-to guides here are the notes Smarc wishes had existed the first time round.