This project documents the setup of a network-wide, privacy-focused DNS sinkhole on a Raspberry Pi Zero 2 W. It combines Pi-hole for ad and malware blocking, Unbound as a local recursive DNS resolver for maximum privacy, and Cloudflared as a secure DNS-over-HTTPS (DoH) fallback.
This setup is optimized for low-resource hardware and hardened to prioritize privacy and security over speed, ensuring that personal DNS data is never leaked to third parties during normal operation.
A client on the network makes a DNS request. The request is processed as follows:
- The query is sent to Pi-hole, which checks it against its blocklists.
- If not blocked, the query is forwarded to the primary upstream resolver, Unbound.
- Unbound resolves the query recursively by contacting the root DNS servers directly, validating each step with DNSSEC.
- If Unbound fails to respond, Pi-hole forwards the query to the secondary upstream resolver, Cloudflared.
- The
strict-orderconfiguration ensures that Cloudflared is only used in a true failure scenario, not in a "race" for the fastest response.
+-------------------+ +----------------+ +------------------+
Your Devices | | | | | |
(Laptop, Phone, IoT) | Pi-hole |----->| Unbound |----->| Root DNS Servers|
| | (Ad/Malware Filter) | | (Recursive DNS)| | (Authoritative) |
| | + strict-order | | + DNSSEC | +------------------+
| +-------------------+ +----------------+
| ^
| |
+-------------------------------+
| (Fallback on Failure Only)
|
v
+----------------+
| Cloudflared |
| (DNS-over-HTTPS)|
+----------------+
- Ultimate Privacy: Unbound acts as a personal recursive resolver, meaning your DNS history is not sent to any third-party provider (like Google, OpenDNS, or even Cloudflare in normal operation).
- Robust Security: Unbound performs DNSSEC validation on all queries, protecting against DNS spoofing and cache poisoning attacks. Pi-hole blocks known malware and phishing domains using curated security blocklists.
- Network-Wide Control: Blocks ads and malicious domains for every device on the network, including smart TVs, game consoles, and IoT devices.
- Resilient Design: Uses Cloudflared as a secure, encrypted DoH fallback in the event that the local Unbound resolver fails.
This repository contains the key custom configuration files created for this project. To replicate this setup, you would first install Pi-hole, Unbound, and Cloudflared, and then apply these configurations.
This file configures Unbound to run as a secure, recursive resolver optimized for a Raspberry Pi Zero 2 W.
File: unbound/pi-hole.conf
(Place in /etc/unbound/unbound.conf.d/ on the Pi)
server:
# Run as a less-privileged user
username: "unbound"
# The path to the root hints file
root-hints: "/var/lib/unbound/root.hints"
# Enable DNSSEC validation
harden-glue: yes
harden-dnssec-stripped: yes
use-caps-for-id: no
# Reduce logging verbosity
verbosity: 0
# Interface and port to listen on
interface: 127.0.0.1
port: 5335
do-ip4: yes
do-udp: yes
do-tcp: yes
# Allowed access control
access-control: 127.0.0.0/8 allow
# Optimization for Raspberry Pi Zero 2 W (512MB RAM)
num-threads: 1
msg-cache-size: 4m
rrset-cache-size: 8m
so-rcvbuf: 1m
# Hide identity and version
hide-identity: yes
hide-version: yes
# Prefetch popular DNS records to improve performance
prefetch: yes
This critical file forces Pi-hole to use its upstream DNS servers in a strict order, solving the "DNS race" problem and ensuring Unbound is always prioritized.
File: dnsmasq/99-strict-order.conf
(Place in /etc/dnsmasq.d/ on the Pi)
strict-order
This optional file optimizes Pi-hole's database by reducing the query retention period, saving memory and disk space.
File: pihole/pihole-FTL.conf
(Place in /etc/pihole/ on the Pi)
MAXDBDAYS=7
This change increases the system's swap file to 1GB to prevent out-of-memory crashes on the Raspberry Pi Zero 2 W.
File: /etc/dphys-swapfile
(This is an existing file. Only the following line should be modified.)
# set size to absolute value, leaving empty (default) then uses computed value
# you most likely don't want this, unless you have an special disk situation
# CONF_SWAPSIZE=100
CONF_SWAPSIZE=1024
During this project, we encountered and solved several key issues.
- Symptom: The initial DNS leak test showed Cloudflare servers, not our own public IP, even though Unbound was configured as the primary resolver.
- Investigation: Using
pihole -tanddig, we confirmed that Unbound was working perfectly on its own. The Pi-hole query log showed that queries were being consistently forwarded to Cloudflared (127.0.0.1#5053). - Root Cause: Pi-hole's default behavior is to forward queries to all upstream servers and use the response from the server that replies the fastest. Cloudflared was consistently winning this "DNS race," so Unbound was rarely being used.
- Solution: We created the configuration file
/etc/dnsmasq.d/99-strict-order.confand added thestrict-orderdirective. This forces Pi-hole to query its upstream servers in the order they are listed, only using the fallback if the primary fails.
- Symptom: Test sites for malware were not being blocked by default.
- Investigation: We concluded that Pi-hole's default blocklists are focused on ads and trackers, not comprehensive security threats.
- Solution: We added a dedicated security blocklist to enhance protection.
- Navigated to Group Management > Adlists.
- Added the URL:
https://phishing.army/download/phishing_army_blocklist_extended.txt - Navigated to Tools > Update Gravity to apply the new list.
To verify that the system is working as intended, perform the following tests.
- Action: Visit dnsleaktest.com and run the Extended Test.
- Expected Result: The test should show only one server, and its IP address should be your network's public IP address. This proves Unbound is handling all your queries.
-
Action: From a client on the network (or the Pi itself), run the following
digcommands. -
Test 1 (Should Succeed):
dig sigok.verteiltesysteme.net
-
Test 2 (Should Fail):
dig sigfail.verteiltesysteme.net
- Action: Visit a modern testing tool like AdBlock Tester.
- Expected Result: A high score, indicating that various types of ad-serving and tracking scripts are being successfully blocked at the DNS level.
- Action: Test a domain directly from one of your security lists.
- View the source of the list we added: Phishing Army Blocklist.
- Pick any domain from that list.
- Attempt to navigate to that domain in your browser.
- Expected Result: Your browser will display an error page, such as "This site can’t be reached" or "Secure Connection Failed." This is the correct and expected behavior, confirming the domain was blocked..

During testing, you may notice that ad-blocking tests (like AdBlock Tester) do not score a perfect 100%. This is expected.
Pi-hole is a DNS-level blocker. It can only block entire domains. It cannot block ads that are served from the same domain as the content you want to see (e.g., YouTube or Facebook ads).
For complete protection, a layered approach is recommended:
- Pi-hole (Network Layer): Blocks the vast majority of ad and malware domains for all devices on your network.
- Browser Extension (Client Layer): Use a reputable content blocker like uBlock Origin in your web browsers. This extension can inspect the content of a page and block the cosmetic ads that Pi-hole cannot, giving you a cleaner browsing experience and a near-perfect score on ad-blocking tests.

