Configure Pi-Hole DNS + Cloudflare DNS over HTTPS (DoH) on a Raspberry Pi
Block ads, trackers, and malware from any local device without having to use an ad-blocker; while securing your DNS traffic at the same time - sounds good!
First, what is Pi-Hole? According to Jacob Salmela, the creator of Pi-Hole:
Pi-hole is a network-wide ad blocker. Instead of installing adblockers on every device and every browser, you can install Pi-hole once on your network, and it will protect all of your devices. Because it works differently than a browser-based ad-blocker, Pi-hole also blocks ads in non-traditional places, such as in games and on smart TVs.
That sounds pretty great!
This guide will cover the following deployment onto a Raspberry Pi (although any Linux-based device/OS can be used):
- Pi-Hole will be installed and used as DNS for all home devices to block ads, trackers, and malware domains.
- DNS over HTTPs (using Cloudflare) will be configured to secure our upstream DNS requests.
Let's get started!
Configure Cloudflare DNS over HTTPS (DoH)
While Pi-Hole will be used as our local DNS server, it will need to query an upstream DNS provider (like Google, or Cloudflare) itself to return a result (provided the query has not already been cached by Pi-Hole). Typically you would set the upstream DNS provider in Pi-Hole to 1.1.1.1 (Cloudflare) or 8.8.8.8 (Google), however these requests are not secured in transit.
We're going to use DNS over HTTPS (DoH) to secure our DNS requests to Cloudflare across our ISP's network to provide us with more privacy.
What is DoH and why should I bother?
The problem with DNS and ISPs
DNS was not designed with security in mind. Queries are sent in plaintext across your ISP's network and are not encrypted or authenticated by default. This is true even if the site you are visiting uses HTTPS: the DNS query to resolve the domain is still sent unencrypted.
Why is this an issue? The same reason why you shouldn't do sensitive things like banking or online shopping on an insecure website: your data can be intercepted, read, and logged at any point in transit. For example, when you visited this webpage on my domain, nathancatania.com, anyone capturing network traffic would see your DNS query to resolve my domain and know that you were attempting to visit it. You can try this yourself, if you are so inclined, with Wireshark.
Many ISPs around the world will log your data, and in many cases are legally required to do so by local governments. Your DNS requests can paint a picture of your internet usage just like your browser history can, and having this logged at any point along can raise significant privacy concerns.
Unsecured DNS also raises the concern of Man-In-The-Middle attacks, where your DNS request could be intercepted and changed without your knowledge or consent. Instead of your requested domain resolving to 1.2.3.4, it might be changed to resolve to 5.6.7.8 instead - which could be a malicious domain or a copy of the original domain designed for phishing. DNSSEC is a mechanism to help prevent this by authenticating that a DNS record has not been altered in transit. However, according to Cloudflare, only a single-digit percentage of domains use DNSSEC today. Additionally, DNSSEC does not provide confidentiality and will not prevent entities from snooping on your DNS requests.
What is DNS over HTTPS (DoH)?
DNS over HTTPS (DoH) is a method of securing your DNS requests, by sending the request to an HTTPS endpoint. This means that your DNS request appears as normal HTTPS (encrypted) web traffic instead of an actual DNS packet. All your ISP sees is secure HTTPS traffic coming from your network: no more DNS traffic that can be snooped on.
The (subjective) issue with DoH
It is worth noting that DoH itself presents some privacy issues as well: There are only a handful of DNS providers that support DoH (Cloudflare, Google, etc) and by using DoH, you would be trusting your DNS traffic to one of these larger centralized entities (although the same would be true if you just set 1.1.1.1 or 8.8.8.8 as your DNS provider anyway): How do you know that these companies are safely and responsibly handling your data? You don't.
There is also the argument that using DoH centralizes DNS to a few larger providers, giving them too much power over the internet as a whole. DNS was designed to be highly distributed across the internet, and the concept of DoH goes against that principle.
What should I do?
This boils down to: Who do you trust more? Your ISP, a company like Cloudflare or Google, or no-one but yourself?
- If you answered "My ISP", then DoH probably isn't for you and you can keep on doing what you've been doing for DNS up until now. You might consider using DoH if your ISP's DNS service offers it.
- If you answered "Cloudflare, Google, etc", then DoH is for you.
- If you answered "No-one but myself", then a solution like Unbound (a recursive and caching DNS resolver) is probably a better solution for you. I don't cover Unbound in this post, but may in the future.
- If all you care about is the "bad guys" not being able to see your data, then DoH is also for you.
In this post, we'll be using Cloudflare DoH.
Trying out Cloudflare DoH
DNS requests occur via an HTTPS endpoint. We can test this using cURL and JSON.
IPv4 (A record) request for example.com
:
curl -H 'accept: application/dns-json' 'https://cloudflare-dns.com/dns-query?name=example.com&type=A'
Response for example.com
:
{
"Status": 0,
"TC": false,
"RD": true,
"RA": true,
"AD": true,
"CD": false,
"Question": [
{
"name": "example.com.",
"type": 1
}
],
"Answer": [
{
"name": "example.com.",
"type": 1,
"TTL": 1409,
"data": "93.184.216.34"
}
]
}
IPv6 (AAAA record) request for example.com
:
curl -H 'accept: application/dns-json' 'https://cloudflare-dns.com/dns-query?name=example.com&type=AAAA'
Response for example.com
:
{
"Status": 0,
"TC": false,
"RD": true,
"RA": true,
"AD": true,
"CD": false,
"Question": [
{
"name": "example.com.",
"type": 28
}
],
"Answer": [
{
"name": "example.com.",
"type": 28,
"TTL": 10226,
"data": "2606:2800:220:1:248:1893:25c8:1946"
}
]
}
Configuring Cloudflare DoH on a Raspberry Pi
The source for much of this was the official Pi-Hole documentation on DoH.
The method detailed here should work for non-Raspberry Pi systems, but you may need to switch out the ARM binary.
Install the cloudflared daemon
We're going to use cloudflared
(or an 'Argo Tunnel' as Cloudflare call it) as our DoH proxy. This will listen for DNS queries on port 5353 (or any custom port you specify), and proxy the requests received to the Cloudflare DoH endpoint. The response received from Cloudflare is then returned via the proxy back to the host that sent the original DNS query.
Why port 5353 and not 53? You can specify any port that isn't in use, apart from port 53. 53 is the standard port for DNS, and Pi-Hole will already be using this port to listen for DNS queries from our local hosts/devices.
- Create a new service account to run the
cloudflared
daemon:
sudo useradd -s /usr/sbin/nologin -r -M cloudflared
- Download the binary tgz for
ARMv6
devices. The download link can be verified here. If you're not using an RPi, then pick a non-ARM binary.
wget https://bin.equinox.io/c/VdrWdbjqyF/cloudflared-stable-linux-arm.tgz
- Unpack the binary, and copy it to the
/usr/local/bin
directory, and make it executable:
tar -xzvf cloudflared-stable-linux-arm.tgz
sudo cp ./cloudflared /usr/local/bin
sudo chmod +x /usr/local/bin/cloudflared
- Change permissions so the
cloudflared
service account can access it:
sudo chown cloudflared:cloudflared /usr/local/bin/cloudflared
- Check the binary is working. This should show the version:
cloudflared -v
> cloudflared version 2019.9.0 (built 2019-09-06-0334 UTC)
If you get a segmentation fault, you may need to compile from source as per the issue reported here.
Create the Configuration File
We need to create a configuration file for cloudflared at /etc/default/cloudflared
which specifies:
- The local port to listen on for DNS requests. These will be proxied upstream to Cloudflare using DoH. As per the Pi-Hole documentation, I used
5053
(although this could be any valid port). - The upstream HTTPS endpoint(s). We'll use
https://1.1.1.1/dns-query
andhttps://1.0.0.1/dns-query
.
The options specified in this file will be passed to the cloudflared
daemon.
Create the configuration file (CTRL+X to save and quit):
sudo nano /etc/default/cloudflared
Add the following:
## Commandline args for cloudflared
CLOUDFLARED_OPTS=--port 5053 --upstream https://1.1.1.1/dns-query --upstream https://1.0.0.1/dns-query
Change the port as required. This will listen for DNS requests on port 5053 (DNS is normally port 53) and will proxy it to either of the 1.1.1.1 or 1.0.0.1 HTTPS endpoints.
Change the permissions for the configuration file so the cloudflared
service account can access it:
sudo chown cloudflared:cloudflared /etc/default/cloudflared
Run at Startup
The above is all well and good, but it requires the cloudflared
daemon to be started manually after each restart and/or error.
Courtesy of Pi-Hole, we can use the below to create a systemd
service that will automatically run on boot and restart on any error.
Create the service file:
sudo nano /lib/systemd/system/cloudflared.service
Add the following:
[Unit]
Description=cloudflared DNS over HTTPS proxy
After=syslog.target network-online.target
[Service]
Type=simple
User=cloudflared
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
Load the service, set it to run at startup, and start the service:
sudo systemctl daemon-reload
sudo systemctl enable cloudflared
sudo systemctl start cloudflared
Check the status:
sudo systemctl status cloudflared
It should be running:
● cloudflared.service - cloudflared DNS over HTTPS proxy
Loaded: loaded (/lib/systemd/system/cloudflared.service; enabled; vendor preset: enabled)
Active: active (running) since Sun 2019-09-29 15:37:44 AEST; 3 weeks 1 days ago
Main PID: 652 (cloudflared)
Tasks: 14 (limit: 2200)
Memory: 35.6M
CGroup: /system.slice/cloudflared.service
└─652 /usr/local/bin/cloudflared proxy-dns --port 5053 --upstream https://1.1.1.1/dns-query --upstream https://1.0.0.1/dns-query
If you encounter an issue, you can view the log output of the service using the following command:
sudo journalctl -u cloudflared
Verify the DNS requests are proxied correctly
To verify, use nslookup
specifying your custom port (5053 above) and 127.0.0.1
(localhost) as the DNS server.
nslookup -port=5053 example.com 127.0.0.1
If everything is working correctly, you should see a response as per the below:
Server: 127.0.0.1
Address: 127.0.0.1#5053
Non-authoritative answer:
Name: example.com
Address: 93.184.216.34
Name: example.com
Address: 2606:2800:220:1:248:1893:25c8:1946
Note that the server is the localhost/Raspberry Pi and the port is 5053 which we defined above. We successfully get a response using these parameters which means DoH has been configured correctly and is working. Testing with example.com
we should see an identical result to our earlier test.
Done!
You now have a DNS proxy running on your Raspberry Pi. If you were to tell clients to use your Raspberry Pi for DNS and to send requests on port 5053 (instead of port 53), they will get a response after the Raspberry Pi forwards the DNS request to Cloudflare over HTTPS.
Troubleshooting
If nslookup
doesn't return anything or looks like it hangs, then your request is not being proxied using DoH. This indicates either a config issue (check the port you specified and whether your HTTPS endpoints in your config file are correct), or you could have an issue with your networking (your specified port could already be in use or the request/response is being blocked by a firewall).
Configure Pi-Hole
In the next step, we will install Pi-Hole and tell it to use 127.0.0.1 (localhost), Port 5053 as its upstream DNS. All DNS requests sent to this location will be proxied using DoH to Cloudflare.
When you're done with this section, you'll be able to set the IP address of your Pi-Hole system (eg: 10.0.0.5) as your DNS provider on your devices, or in your router/modem, and all ads on the web will magically disappear!
Requirements
There are a couple of things you'll need to check and have in place before continuing.
Check your Network Interfaces
Your Raspberry Pi (or similar instance) probably has multiple network interfaces. In the case of the RPi, you'll have at least 3: loopback/localhost (lo0
), ethernet (eth0
), and wireless (wlan0
). You'll need to note down the interface that Pi-Hole will use and listen for incoming DNS requests on.
I would strongly advise you to NOT use wireless or Wi-Fi for Pi-Hole, and instead use a wired connection (eth0
or similar).
nathan@pi:~ $ ip -c address
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether b8:27:eb:XX:XX:XX brd ff:ff:ff:ff:ff:ff
inet 10.0.0.5/24 brd 10.0.0.255 scope global noprefixroute eth0
valid_lft forever preferred_lft forever
inet6 fe80::e0bc:::/64 scope link
valid_lft forever preferred_lft forever
3: wlan0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN group default qlen 1000
link/ether b8:27:eb:XX:XX:XX brd ff:ff:ff:ff:ff:ff
Assign a Static IP Address
The system that Pi-Hole is installed on must have a static IP address, or it's current IP address reserved in your DHCP server or modem/router. You'll be pointing all of your devices to use Pi-Hole as their DNS, so if Pi-Hole's IP address changes, all of your devices will break.
To set a static IP on the Raspberry Pi, edit /etc/dhcpcd.conf
:
nathan@pi:~ $ sudo nano /etc/dhcpcd.conf
Define a static IP, gateway, and DNS under "Example static IP configuration", and (optionally) define the hostname:
[..snip...]
## Inform the DHCP server of our hostname for DDNS.
pi.yourdomain.internal
[..snip..]
## Example static IP configuration:
interface eth0
static ip_address=10.0.0.5/24
#static ip6_address=fd51:42f8::::/64
static routers=10.0.0.1
static domain_name_servers=1.1.1.1 1.0.0.1
Use CTRL+X
then Y
to exit. Reboot when you have finished:
nathan@pi:~ $ sudo reboot
Download the Pi-Hole installer
For reference, you may want to have a read of the Pi-Hole documentation.
Download and run the Pi-Hole installer:
wget -O basic-install.sh https://install.pi-hole.net
...
sudo bash basic-install.sh
Configure the Installer
Upon running the installer, you'll be taken to a colored screen. Follow the prompts and the instructions below to install Pi-Hole.
- When prompted, select the network interface to use for Pi-Hole (recommended:
eth0
for an RPi):
- For the Upstream DNS Provider, select anything for the time being (Google, Cloudflare, etc). We will change this later.
- For the blocklists, leave the default selected and continue:
-
Select whether to enable IPv4 and/or IPv6. If you're not sure, leave this option as the default (both options selected).
-
The following step will ask you to confirm the Static IP address and Gateway. The IP and Gateway displayed on-screen should match the static IP you set earlier. If not, you can alter it here (most likely you selected the wrong interface at Step 1).
Most of the remaining configuration can be left as the default:
- Ensure the web interface is installed. In the following step, ensure you also install the webserver (Lighttpd).
- Ensure queries are logged. If you have tight or severe security concerns you might want to disable this.
- When prompted to select a privacy mode, the setting is up to you.
- Everything is stored locally on the Pi-Hole device, so for some lovely analytics, you might want to select "Show everything".
- Conversely, if you are concerned about the privacy of the logs, you might want to select settings 1, 2, or 3.
At this point, your configuration is done and Pi-Hole will finish installing. When the process is finished, you'll get one final screen with your default admin credentials.
Ignore the default password: You should change it to something more secure. You can change (or reset) the password from the command-line:
sudo pihole -a -p
Setting a blank password will disable the password requirement for the Admin UI (not recommended).
Adding Firewall Rules
Depending on your device, you may need to permit inbound connections from TCP 80 and UDP 53. This will allow you to access the Web UI and for Pi-Hole to receive DNS queries from devices.
If you're using a Raspberry Pi, you can do this using ufw
:
sudo ufw limit ssh
sudo ufw allow dns
sudo ufw allow 80/tcp
The first line will allow through SSH connections for management. You may or may not want to do this. As Pi-Hole is not exposed inbound from the internet and is local to your home network, this should be OK from a security standpoint.
Lastly, you need to enable ufw
for the settings to take effect:
sudo ufw enable
You can check the status of ufw
and it's associated rules using the below command:
nathan@pi:~ $ sudo ufw status
Status: active
To Action From
-- ------ ----
22/tcp LIMIT Anywhere
80/tcp ALLOW Anywhere
DNS ALLOW Anywhere
22/tcp (v6) LIMIT Anywhere (v6)
80/tcp (v6) ALLOW Anywhere (v6)
DNS (v6) ALLOW Anywhere (v6)
Access the Admin Web Interface
Open your web browser and navigate to:
http://<pi-hole-ip>/admin
Where <pi-hole-ip>
is the static IP address you set for Pi-Hole. The admin UI should appear.
- If you get a blank screen with the Pi-Hole logo only, make sure you added the
/admin
at the end of the URL.
If you're getting a CONNECTION_REFUSED
error or similar, check to see that you have configured your firewall rules correctly to allow inbound connections on port 80.
Alternatively, check the other IP addresses of any other network interfaces you have; wlan0
, lo0
etc. You may have selected the wrong interface when installing Pi-Hole. You can re-run the installer again to fix this.
Click Login in the side panel to log into the Dashboard using the admin password you set earlier.
Your Dashboard will start to populate data once your devices start using Pi-Hole for DNS.
If you notice that some sites stop working once you start using Pi-Hole, you can bypass the block under Whitelist.
Managing Blocks (Adlists)
To manage/add/remove Adlists (lists of domains that should be blocked), go to Group Management > Adlists. The two default adlists should be listed. Here are some other common lists:
http://sysctl.org/cameleon/hosts
https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt
https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt
https://hosts-file.net/ad_servers.txt
Anything listed as an entry in any of your Adlists will be blocked.
Set Cloudflare DoH as the Upstream DNS provider
We now need to tell Pi-Hole to use our DoH configuration for DNS queries.
Under Settings, click the DNS tab. De-select everything under Upstream DNS Servers and then add the following as a custom server:
127.0.0.1#5053
Replace 5053
with whatever port you set the cloudflared
daemon to listen on for requests.
Under Interface listening behavior select the option to Listen only on interface eth0 (or whatever interface you configured Pi-Hole on).
Lastly under Advanced DNS settings, check the box to enable the first 3 options:
- Never forward non-FQDNs
- Never forward reverse lookups for private IP ranges
- Use DNSSEC
Verify DNS resolution is functioning correctly
On another device, manually set the DNS to point to the IP address of your Pi-Hole system, eg: 10.0.0.5. Alternatively, alter the dhcpcd.conf
file on your RPi to point to its IP address.
You should start to see DNS query traffic within the Pi-Hole Dashboard. Try querying example.com
:
nathan@pi:~ $ nslookup example.com
Server: 10.0.0.5
Address: 10.0.0.5#53
Non-authoritative answer:
Name: example.com
Address: 93.184.216.34
Name: example.com
Address: 2606:2800:220:1:248:1893:25c8:1946
You can also review the Query Log in the admin UI:
Troubleshooting
If nslookup
doesn't return anything or looks like it hangs, then your request is not being proxied through Cloudflare DoH. Check that cloudflared
is running and that you can query it directly from the Pi-Hole host:
nslookup -port=5053 example.com 127.0.0.1
If this fails, there could be a cloudflared
config issue. Check the port you specified and whether the DoH endpoints/URLs are correct in the config file.
If the above command returns a result, then your issue is localized to Pi-Hole itself. Make sure any firewall in use (including ufw
) is permitting DNS traffic inbound to the Pi-Hole host. DNS is port 53 (typically UDP, but TCP can be used as a fallback).
nathan@pi:~ $ sudo ufw status
Status: active
To Action From
-- ------ ----
DNS ALLOW Anywhere
...
nathan@pi:~ $ netstat -ln | grep 53
tcp 0 0 0.0.0.0:53 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:5053 0.0.0.0:* LISTEN
udp 0 0 0.0.0.0:53 0.0.0.0:*
udp 0 0 0.0.0.0:5353 0.0.0.0:*
udp 0 0 0.0.0.0:53149 0.0.0.0:*
udp 0 0 127.0.0.1:5053 0.0.0.0:*
You can also use the pihole
command to manage Pi-Hole from the command-line.
Check the status of Pi-Hole:
nathan@pi:~ $ pihole status
[✓] DNS service is running
[✓] Pi-hole blocking is Enabled
Debug Pi-Hole (this produces a LOT of information for you to parse):
nathan@pi:~ $ pihole debug
[..snip..]
********************************************
********************************************
[✓] ** FINISHED DEBUGGING! **
[?] Would you like to upload the log? [y/N] n
* Log will NOT be uploaded to tricorder.
* A local copy of the debug log can be found at: /var/log/pihole_debug.log
nathan@pi:~ $ cat /var/log/pihole_debug.log
...
...
You can also try restarting the DNS service and subsystems:
nathan@pi:~ $ pihole restartdns
Finish
You should now have a working Pi-Hole deployment that forwards requests upstream to Cloudflare using DoH.
The last thing you need to do is get all of your devices to use your Pi-Hole DNS.
You could do this manually by setting the DNS on each device, or you could go the easy route and set your DHCP server (eg: your ISP modem/router) to use the Pi-Hole IP instead. This way, when a device obtains it's network settings via DHCP, it will automatically get the Pi-Hole IP address for it's DNS settings without you having to reconfigure every device manually.