How to do DNS over HTTPS on a Raspberry Pi

Last year, at the recommendation of a work colleague, I grabbed one of my spare Raspberry Pi 4s and installed the DNS proxy and content blocker Pi-Hole. It’s now handling all the DNS queries on my home network. Recently, I upgraded my Pi-Hole server to make its DNS requests over HTTPS.

This technique encrypts DNS queries in the same way that web pages are requested securely. Intermediate parties, like your ISP, can’t track the websites and services you’re accessing as they can with with regular DNS, which sends queries in clear text. You encrypt your wireless network traffic, you encrypt web site requests — through HTTPS — so you should encrypt DNS lookups too.

With DNS-over-HTTPS (DoH) set up at home, the question I next asked was, what about other locations? I can’t take my Pi-Hole with me, and I don’t have access to office networks, so can I do DoH on a client machine? The answer is, yes, as I learned when I tried it on the Mac I use in my office. I have a Raspberry Pi 400 there too — what about that? The same process applies, but there’s an extra hoop to jump through thanks to the way Raspberry Pi OS handles DHCP and DNS lookups.

I use Cloudflare’s cloudflared to drive DoH because it’s what I use alongside the Pi-Hole. It’s an open source cross-platform DNS proxy that routes DNS queries to wherever you’d like to route them. Cloudflare has the IP address 1.1.1.1 for DNS queries, but supports DoH at 1.1.1.1/dns-query. Google likewise accepts DoH queries at 8.8.8.8/dns-query. So ‘all’ you need to do is install cloudflared, configure it to run at startup, and set it up to route DNS queries from browsers, apps and so forth to a DoH site like those mentioned above.

Installing cloudflared is straightforward. Grab the installer file from Cloudflare and run it:

wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-arm64.deb
sudo dpkg -i *.deb

Note I’ve included the URL for the 64-bit version of cloudflared.

Update The process for install the 32-bit version is slightly different:

wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-arm
sudo cp ./cloudflared-linux-arm /usr/local/bin/cloudflared
sudo chmod +x /usr/local/bin/cloudflared

You can check that you’re up to date with cloudflared --version and subsequently update the proxy with cloudflared update.

Configuring it is done like this: create a config file which you’ll subsequently tell the system to load into cloudflared at startup:

sudo nano /etc/default/cloudflared

To this file, add the line:

CLOUDFLARED_OPTS=--upstream https://1.1.1.1/dns-query --upstream https://8.8.8.8/dns-query

Here I’ve set two DNS targets: Cloudflare’s and, as backup, Google’s. Save the file: hit ctrlc, y and hit Enter.

Now you set up a service to run cloudflared at startup. First, run this code, to generate the service definition:

sudo tee /etc/systemd/system/cloudflared.service >/dev/null <<EOF
[Unit]
Description=DNS over HTTPS (DoH) proxy client
Wants=network-online.target nss-lookup.target
Before=nss-lookup.target
After=network-online.target syslog.target

[Service]
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
DynamicUser=yes
EnvironmentFile=/etc/default/cloudflared
ExecStart=/usr/local/bin/cloudflared proxy-dns $CLOUDFLARED_OPTS
Restart=on-failure
RestartSec=10
KillMode=process

[Install]
WantedBy=multi-user.target
EOF

The following command starts the service:

sudo systemctl enable --now cloudflared

You can test your setup is working at this point with the dig tool. This won’t be installed on your Pi — you can get it now with:

sudo apt update && sudo apt install dnsutils -y

You can then run the following command to get Cloudflare’s IPv6 IP address. It will come up almost instantaneously. If there’s a long wait, check you started the service and the you entered the config files correctly.

dig +short @127.0.0.1 cloudflare.com AAAA

The DNS proxy is set up and running, but it’s not yet connected to the system. To do so, edit the Pi’s DHCP daemon configuration:

sudo nano /etc/dhcpcd.conf

Scroll to the end of the file and add the following lines:

interface eth0
static domain_name_servers=127.0.0.1

interface wlan0
static domain_name_servers=127.0.0.1

This tells dhcpd to send DNS requests to localhost, which is where cloudflared is listening — as the earlier test confirmed. Save the config file and then reboot.

When your Pi is back up, you can check dhcpd is doing the right thing by looking at the contents of the file /etc/resolv.conf. It’ll have the line nameserver 127.0.0.1. Now you can fire up a browser or update apt and know the server address requests are being encrypted and relayed to a verified server. You can also use dig:

dig +short cloudflare.com AAAA

Note that this time you don’t tell dig which DNS server to use (the @127.0.0.1 in the earlier call) so that it’ll use the system-specified one, ie. the one set through your DHCPCD changes.