Pi-hole 6, DoH, and Unifi
I recently upgraded both of my Pi-Holes to version 6.
Not much to really write about, but there are some differences for some edge use cases, none of which impacted myself.
I wanted to implement DNS over HTTPS (DoH), which adds an additional layer of security to your network.
If you are running Pi-hole in Docker, see this post: https://www.nodinrogers.com/post/2025-09-15-pihole6-doh-docker/
DNS and DoH basics
As a refresher, some DNS basics...
DNS query/response
You open your web browser and go to https://yahoo.com.
Your computer has no idea what a yahoo.com is, but it does know what an IP address is, and DNS translates hostnames (yahoo.com) to an IP address.
Your computer, under the hood, uses the IP address associated with yahoo.com and sends you on your merry way.
The problem with traditional DNS
However, DNS queries/responses are sent in plain text by default.
Easy to troubleshoot, but also easy to see/manipulate for any bad actor.
As you can see from the packet capture snippet below, DNS queries/responses are very easy to read.
No big deal, but you have some Internet Service Providers (ISP) that track this information, and sell it to advertisers.
Worse, unencrypted DNS traffic can be intercepted and misdirected.
For example, if you wanted to go to your online bank website, and a bad actor intercepted the DNS query, they would provide an IP address that would send you to a website that looks like your bank's website, but is actually a fraudulent website, trying to extract personal information.
Hello DNS over HTTPS (DoH)
To prevent us from being tracked, and to secure out internet activity, we can implement DNS over HTTPS (DoH), which sends DNS queries/responses over HTTPS (encrypted).
Someone trying to snoop in on your DNS queries/responses after that is only going to see HTTP data:
Traffic flow with DNS using DoH
Locally, your computer will send an unencrypted DNS request to the DNS server (Pi-hole), and Pi-hole, assuming it doesn't have an entry for that hostname already, will send an encrypted DNS (using DoH) query to our configured upstream DNS server (Cloudflare).
DNS queries will following this traffic pattern:
-
Client >>> local DNS server (Pi-hole), unencrypted DNS query, using port 53
-
Local DNS server (Pi-hole) >>> upstream DNS server (Cloudflare), encrypted DNS query, using port 443
-
Upstream DNS server (Cloudflare) >>> Local DNS server (Pi-hole), encrypted DNS response, using port 443
-
Local DNS server (Pi-hole), unencrypted DNS response, using port 53
Pi-hole on UDM firmware version 3.0+
Since firmware version 3.x on the Unifi UDM/UDM Pro, podman is no longer available, so we had to use another method to create a container.
I wrote earlier about running Pi-hole in a systemd-nspawn container: https://www.nodinrogers.com/post/2025-07-20-pihole-udm-unifi-3.x/
This guide will configure/enable DoH for a Pi-hole instance running in this container.
Implement DoH for Pi-hole running on a Unifi UDM
- Spawn a shell to your container
machinectl shell debian-custom
- Install
wget
apt install wget -y
- Download the
cloudflaredbinary for amd64, change permissions, and move it to /usr/local/bin
wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-arm64
cp cloudflared-linux-arm64 /usr/local/bin/cloudflared
chmod +x /usr/local/bin/cloudflared
- Verify
cloudflaredis working
cloudflared -v
cloudflared version 2025.8.1 (built 2025-08-21-1535 UTC)
- Add a cloudflared user
useradd -s /usr/sbin/nologin -r -M cloudflared
- Create /etc/default/cloudflared file
# Commandline args for cloudflared, using Cloudflare DNS
CLOUDFLARED_OPTS=--port 5053 --upstream https://cloudflare-dns.com/dns-query
- Update the permissions for the configuration file and
cloudflaredbinary to allow access for the cloudflared user
chown cloudflared:cloudflared /etc/default/cloudflared
chown cloudflared:cloudflared /usr/local/bin/cloudflared
- Create /etc/systemd/system/cloudflared.service file
[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
- Enable the systemd service to run on startup, then start the service and check its status
systemctl enable cloudflared
systemctl start cloudflared
systemctl status cloudflared
● cloudflared.service - cloudflared DNS over HTTPS proxy
Loaded: loaded (/etc/systemd/system/cloudflared.service; enabled; preset: enabled)
Active: active (running) since Mon 2025-09-15 08:52:58 MDT; 3s ago
Invocation: 5f3e0a2d6cd947968d059cd584d9b88a
Main PID: 107360 (cloudflared)
CGroup: /system.slice/cloudflared.service
└─107360 /usr/local/bin/cloudflared proxy-dns --port 5053 --upstream https://cloudflare-dns.com/dns-query
Sep 15 08:52:58 unifi-container systemd[1]: Started cloudflared.service - cloudflared DNS over HTTPS proxy.
Sep 15 08:52:58 unifi-container cloudflared[107360]: 2025-09-15T14:52:58Z INF Adding DNS upstream url=https://cloudflare-dns.com/dns-query
Sep 15 08:52:58 unifi-container cloudflared[107360]: 2025-09-15T14:52:58Z INF Starting DNS over HTTPS proxy server address=dns://localhost:5053
Sep 15 08:52:58 unifi-container cloudflared[107360]: 2025-09-15T14:52:58Z INF Starting metrics server on 127.0.0.1:33067/metrics
- Configure Pi-hole to use the local cloudflared service as the upstream DNS server by specifying 127.0.0.1#5053 as the Custom DNS (IPv4).
Settings >>> DNS, under Upstream DNS Servers, uncheck all DNS servers.
Under Custom DNS servers, add:
127.0.0.1#5053
Click Save & Apply at the bottom.
Test new settings
- From within the container, test
cloudflaredto make sure it is working:
dig @127.0.0.1 -p 5053 www.google.com
; <<>> DiG 9.20.11-1-Debian <<>> @127.0.0.1 -p 5053 www.google.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 24078
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
; COOKIE: fcd341b364321773 (echoed)
;; QUESTION SECTION:
;www.google.com. IN A
;; ANSWER SECTION:
www.google.com. 5 IN A 142.250.72.4
;; Query time: 190 msec
;; SERVER: 127.0.0.1#5053(127.0.0.1) (UDP)
;; WHEN: Mon Sep 15 08:53:59 MDT 2025
;; MSG SIZE rcvd: 85
- From the unifi host (outside the container), test Pi-hole to ensure that it works
dig @10.0.0.5 www.google.com
; <<>> DiG 9.20.11-1-Debian <<>> @10.0.0.5 www.google.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 59283
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
; COOKIE: e38e8c30dd83e4ab (echoed)
;; QUESTION SECTION:
;www.google.com. IN A
;; ANSWER SECTION:
www.google.com. 296 IN A 142.250.72.68
;; Query time: 10 msec
;; SERVER: 10.0.0.5#53(10.0.0.5) (UDP)
;; WHEN: Mon Sep 15 08:55:45 MDT 2025
;; MSG SIZE rcvd: 85
- Using a browser, simply open https://1.1.1.1/help and look at the results.
If everything is configured correctly, you should see Yes for the section Using DNS over HTTPS (DoH):
References
nodinrogers.com / Running Pi-hole on the UDM in Unifi 3.0+
https://www.nodinrogers.com/post/2025-07-20-pihole-udm-unifi-3.x/
Github / unifi-utilities / unifios-utilities / nspawn-container
https://github.com/unifi-utilities/unifios-utilities/tree/main/nspawn-container
Pi-hole documentation / Guides / DNS / cloudflared (DoH)
https://docs.pi-hole.net/guides/dns/cloudflared/