Your ISP Is Still Reading Your DNS Queries
You’ve got Pi-hole running. Ads are blocked. You feel like a network wizard. And then you remember: every DNS query your Pi-hole forwards upstream goes out completely unencrypted. Your ISP can read every hostname your household resolves. So can anyone else sitting between you and your upstream resolver.
That’s where DNS-over-HTTPS (DoH) comes in — and specifically, the two tools you’ll actually use to set it up at home: cloudflared and dnscrypt-proxy.
Both sit between your local resolver (Pi-hole, AdGuard Home, Unbound) and the public internet, wrapping your queries in HTTPS or other encrypted transports before they leave your network. But they’re built with very different philosophies, and choosing the wrong one means either unnecessary complexity or unnecessary lock-in.
Let’s walk through both, set them up, and figure out which one belongs in your homelab.
What We’re Actually Solving
Before picking a tool, be honest about your threat model.
DoH protects you from:
- Your ISP logging and selling your DNS query history
- Passive surveillance on your local network (coffee shop, hotel, nosy router firmware)
- DNS hijacking by your ISP redirecting certain queries
DoH does NOT protect you from:
- The upstream resolver you’re sending queries to — they still see everything
- TLS SNI leaking the hostname when you actually connect to a site (unless your browser/OS does Encrypted Client Hello)
- A compromised Pi-hole or router inside your own network
So the honest answer is: DoH hides your queries from your ISP and local eavesdroppers, but not from whoever runs the upstream resolver you’re using. Keep that in mind when choosing a provider.
Option 1: cloudflared
cloudflared is Cloudflare’s official tunnel client. You probably know it for Cloudflare Tunnels, but it also ships a proxy-dns mode that turns it into a local DoH forwarder. Simple, opinionated, and it only speaks to Cloudflare’s resolvers.
Install
# Debian/Ubuntuwget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.debsudo dpkg -i cloudflared-linux-amd64.deb
# Or via package manager on some distrossudo apt install cloudflaredRun It
cloudflared proxy-dns --port 5053 --upstream https://1.1.1.1/dns-query --upstream https://1.0.0.1/dns-queryThat’s it. cloudflared now listens on 127.0.0.1:5053 and forwards all queries over DoH to Cloudflare’s 1.1.1.1. Point your Pi-hole upstream there and you’re done.
Systemd Unit
You don’t want to babysit this in a terminal. Drop a unit file:
sudo cloudflared service installsudo systemctl enable --now cloudflaredOr write your own if you want custom flags:
[Unit]Description=cloudflared DNS-over-HTTPS proxyAfter=network-online.targetWants=network-online.target
[Service]ExecStart=/usr/bin/cloudflared proxy-dns \ --port 5053 \ --upstream https://1.1.1.1/dns-query \ --upstream https://1.0.0.1/dns-queryRestart=on-failureUser=nobodyDynamicUser=yes
[Install]WantedBy=multi-user.targetSave that to /etc/systemd/system/cloudflared.service, then:
sudo systemctl daemon-reloadsudo systemctl enable --now cloudflaredPoint Pi-hole at It
In Pi-hole’s admin panel: Settings → DNS → Custom upstream DNS servers. Add:
127.0.0.1#5053Uncheck all other upstreams. Done.
For AdGuard Home: Settings → DNS Settings → Upstream DNS servers → add 127.0.0.1:5053.
Verify It Works
dig @127.0.0.1 -p 5053 example.comYou should get a real answer. To confirm it’s actually going out encrypted:
sudo tcpdump -i lo port 5053 -nTraffic on port 5053 is the plain DNS between Pi-hole and cloudflared — that’s fine, it’s localhost. The outbound encryption happens on port 443 toward Cloudflare. To verify that:
sudo tcpdump -i eth0 host 1.1.1.1 -nYou should see only port 443 traffic, no port 53.
The Honest Limitation
cloudflared is Cloudflare or bust. The --upstream flag accepts any DoH URL in theory, but the binary is built and maintained by Cloudflare, optimized for their infrastructure, and their documentation basically assumes you’re using 1.1.1.1. If you want Quad9, Mullvad, or your own resolver, cloudflared will technically work but you’re swimming against the current.
Also: Cloudflare now knows everything your household resolves. You traded your ISP’s surveillance for Cloudflare’s. They have a decent privacy policy, but that’s still a significant trust decision.
Option 2: dnscrypt-proxy
dnscrypt-proxy is the Swiss Army knife of encrypted DNS. It supports DoH, DoT (DNS-over-TLS), DNSCrypt (its own encrypted protocol), and ODoH (Oblivious DoH — more on that in a second). It ships with a curated list of public resolvers, does automatic load balancing, timeout-based failover, and doesn’t care which provider you use.
This is the one that actually solves provider lock-in.
Install
# Debian/Ubuntusudo apt install dnscrypt-proxy
# Or grab the binary directlywget https://github.com/DNSCrypt/dnscrypt-proxy/releases/latest/download/dnscrypt-proxy-linux_x86_64-2.x.x.tar.gzThe package version in most distros is a bit behind but functional. The GitHub release is always current.
Configure It
The main config lives at /etc/dnscrypt-proxy/dnscrypt-proxy.toml. It’s heavily commented and intimidating, but you only need to touch a handful of settings.
# Listen on localhost port 5053 so Pi-hole can still own port 53listen_addresses = ['127.0.0.1:5053']
# Pick your resolvers from the public list# Options: quad9-dnscrypt, mullvad-doh, adguard-dns, cloudflare, etc.server_names = ['quad9-dnscrypt', 'mullvad-doh', 'adguard-dns-doh']
# Require servers that don't log queriesrequire_nolog = true
# Require servers that don't filter results (unless you want filtering)require_nofilter = false
# Enable DNSSEC validationrequire_dnssec = true
# Load balance across multiple serverslb_strategy = 'p2' # pick 2 fastestlb_estimator = true
# Automatic resolver list updates[sources] [sources.public-resolvers] urls = ['https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/public-resolvers.md'] cache_file = '/var/cache/dnscrypt-proxy/public-resolvers.md' minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3' refresh_delay = 72 prefix = ''Then enable and start it:
sudo systemctl enable --now dnscrypt-proxyOblivious DoH (ODoH) — The Actually Interesting Part
Regular DoH still tells the upstream resolver your IP address. Oblivious DoH (ODoH) adds a relay in between: your client encrypts the query such that only the target resolver can decrypt it, but routes it through an oblivious relay that strips your IP. The relay sees your IP but not the query. The resolver sees the query but not your IP. Neither one has the full picture.
This is the closest you get to actual privacy from the upstream resolver, without routing everything through Tor.
To enable ODoH in dnscrypt-proxy:
# Add ODoH relays[anonymized_dns] routes = [ { server_name = 'odoh-cloudflare', via = ['odoh-relay-fastly'] }, { server_name = 'odoh-quad9', via = ['odoh-relay-cloudflare'] } ]The relay and target are operated by different organizations, so collusion would be required to de-anonymize you. Honestly, for a home lab, this is probably overkill — but it’s a genuinely cool protocol and dnscrypt-proxy is one of the few clients that supports it out of the box.
DNS-over-Tor
Yes, dnscrypt-proxy can route queries through Tor if you have a SOCKS5 proxy running locally (Tor Browser, tor service, etc.):
[proxy] proxy = 'socks5://127.0.0.1:9050'This adds significant latency (Tor is not fast) and is way overkill for most home setups, but it’s there if you need it.
Point Pi-hole at dnscrypt-proxy
Same as cloudflared — in Pi-hole’s DNS settings, set the upstream to 127.0.0.1#5053. For AdGuard Home, use 127.0.0.1:5053.
Verify
# Check it's listeningsudo ss -ulnp | grep 5053
# Query through itdig @127.0.0.1 -p 5053 example.com
# Check which server actually answereddig @127.0.0.1 -p 5053 example.com +stats | grep SERVER
# Watch the traffic (plain DNS on loopback — fine)sudo tcpdump -i lo port 5053 -nFor the upstream verification, dnscrypt-proxy logs which server handled each query at debug log level:
sudo journalctl -u dnscrypt-proxy -fHead-to-Head
| Feature | cloudflared | dnscrypt-proxy |
|---|---|---|
| Setup complexity | Very low | Moderate |
| Provider lock-in | Cloudflare only | Any provider |
| Protocols | DoH | DoH, DoT, DNSCrypt, ODoH |
| ODoH support | No | Yes |
| DNS-over-Tor | No | Yes |
| Automatic failover | Basic (two upstreams) | Yes, with latency-based lb |
| Memory footprint | ~20 MB | ~15 MB |
| Curated resolver list | No | Yes (auto-updated) |
| Pi-hole integration | Trivial | Trivial |
| AdGuard integration | Trivial | Trivial |
| DNSSEC validation | Passed to upstream | Local validation |
| Active development | Yes (Cloudflare) | Yes (open source) |
Memory footprint is roughly equivalent — both are lightweight enough that you’d run them happily on a Pi Zero. Latency depends far more on your upstream resolver and your internet connection than on which forwarder you choose.
A Note on Unbound
If you’re running Unbound as your recursive resolver (doing the full DNS resolution chain yourself instead of forwarding to a public resolver), you don’t need either of these tools. Unbound speaks to root servers directly, and you can configure it to do DoT toward individual zones.
But if you’re forwarding to a public resolver — which most Pi-hole and AdGuard setups do — you need one of these two to encrypt that hop.
The Bottom Line
Use cloudflared if:
- You’re already bought into Cloudflare’s ecosystem (Tunnels, Zero Trust, etc.)
- You want the simplest possible setup — one binary, one command, done
- You’re fine with Cloudflare as your upstream resolver and trust their privacy policy
- You’re setting this up for someone else who will never touch the config again
Use dnscrypt-proxy if:
- You want to choose your upstream resolver (Quad9, Mullvad, AdGuard, or others)
- Provider independence matters to you — today you trust Cloudflare, tomorrow maybe you don’t
- You want ODoH’s relay-based anonymization
- You like the idea of automatic failover and latency-based load balancing across multiple resolvers
- You’re the type who actually reads config files for fun (we see you)
Honestly, for most homelabs where privacy from your ISP is the goal, dnscrypt-proxy is the better default. It doesn’t require you to place all your trust in a single company, it supports better protocols, and the extra configuration is a one-time cost. cloudflared’s strength is simplicity, but simplicity that comes with permanent lock-in is a trade-off worth being conscious of.
Either way, your ISP is about to have a very boring DNS log.