Skip to content
Go back

DNS-over-HTTPS at Home: cloudflared vs dnscrypt-proxy

By SumGuy 9 min read
DNS-over-HTTPS at Home: cloudflared vs dnscrypt-proxy

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:

DoH does NOT protect you from:

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

Terminal window
# Debian/Ubuntu
wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
sudo dpkg -i cloudflared-linux-amd64.deb
# Or via package manager on some distros
sudo apt install cloudflared

Run It

Terminal window
cloudflared proxy-dns --port 5053 --upstream https://1.1.1.1/dns-query --upstream https://1.0.0.1/dns-query

That’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:

Terminal window
sudo cloudflared service install
sudo systemctl enable --now cloudflared

Or write your own if you want custom flags:

[Unit]
Description=cloudflared DNS-over-HTTPS proxy
After=network-online.target
Wants=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-query
Restart=on-failure
User=nobody
DynamicUser=yes
[Install]
WantedBy=multi-user.target

Save that to /etc/systemd/system/cloudflared.service, then:

Terminal window
sudo systemctl daemon-reload
sudo systemctl enable --now cloudflared

Point Pi-hole at It

In Pi-hole’s admin panel: Settings → DNS → Custom upstream DNS servers. Add:

127.0.0.1#5053

Uncheck all other upstreams. Done.

For AdGuard Home: Settings → DNS Settings → Upstream DNS servers → add 127.0.0.1:5053.

Verify It Works

Terminal window
dig @127.0.0.1 -p 5053 example.com

You should get a real answer. To confirm it’s actually going out encrypted:

Terminal window
sudo tcpdump -i lo port 5053 -n

Traffic 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:

Terminal window
sudo tcpdump -i eth0 host 1.1.1.1 -n

You 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

Terminal window
# Debian/Ubuntu
sudo apt install dnscrypt-proxy
# Or grab the binary directly
wget https://github.com/DNSCrypt/dnscrypt-proxy/releases/latest/download/dnscrypt-proxy-linux_x86_64-2.x.x.tar.gz

The 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.

/etc/dnscrypt-proxy/dnscrypt-proxy.toml
# Listen on localhost port 5053 so Pi-hole can still own port 53
listen_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 queries
require_nolog = true
# Require servers that don't filter results (unless you want filtering)
require_nofilter = false
# Enable DNSSEC validation
require_dnssec = true
# Load balance across multiple servers
lb_strategy = 'p2' # pick 2 fastest
lb_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:

Terminal window
sudo systemctl enable --now dnscrypt-proxy

Oblivious 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

Terminal window
# Check it's listening
sudo ss -ulnp | grep 5053
# Query through it
dig @127.0.0.1 -p 5053 example.com
# Check which server actually answered
dig @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 -n

For the upstream verification, dnscrypt-proxy logs which server handled each query at debug log level:

Terminal window
sudo journalctl -u dnscrypt-proxy -f

Head-to-Head

Featurecloudflareddnscrypt-proxy
Setup complexityVery lowModerate
Provider lock-inCloudflare onlyAny provider
ProtocolsDoHDoH, DoT, DNSCrypt, ODoH
ODoH supportNoYes
DNS-over-TorNoYes
Automatic failoverBasic (two upstreams)Yes, with latency-based lb
Memory footprint~20 MB~15 MB
Curated resolver listNoYes (auto-updated)
Pi-hole integrationTrivialTrivial
AdGuard integrationTrivialTrivial
DNSSEC validationPassed to upstreamLocal validation
Active developmentYes (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:

Use dnscrypt-proxy if:

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.


Share this post on:

Send a Webmention

Written about this post on your own site? Send a webmention and it'll show up above once verified.


Next Post
iperf3 + nload: Network Diagnosis

Discussion

Powered by Garrul . Sign in with GitHub or Google, or post anonymously.

Related Posts