Block ads on your whole network with Blocky on a Raspberry Pi
Set up Blocky on a Raspberry Pi as your network DNS resolver. Blocks ads and trackers for every device on your LAN, including phones and TVs that can't run blockers.
# WHAT YOU'LL NEED
- A Raspberry Pi 3, 4, or 5 with Ethernet. Wired only; a DNS box on WiFi is asking for trouble.
- microSD card, 8 GB or larger.
- Raspberry Pi Imager on your desktop.
- An Ethernet cable.
- A USB-C (Pi 4/5) or micro-USB (Pi 3) power supply.
- Admin access to your home router so you can change the DHCP DNS setting.
- Comfort with a terminal and SSH.
Blocky is a single Go binary that does network-wide DNS filtering. No database, no web UI, one YAML config file. It speaks DNS-over-HTTPS and DNS-over-TLS to upstreams natively, starts in under a second, and idles around 30 MB of RAM. Pi-hole has a web UI; Blocky has a YAML file. Different tastes.
This guide walks through a fresh install on a Raspberry Pi running Raspberry Pi OS Lite. The end state: every device on your home network resolves DNS through the Pi, with ads and trackers blocked at the resolver. If you want a web dashboard or per-client rules, Pi-hole and AdGuard Home are reasonable alternatives. This guide is about Blocky.
A used Pi 3 works fine for a household. A Pi 4 is overkill for DNS but you probably have one.
Step 1: Flash Raspberry Pi OS Lite
In Raspberry Pi Imager:
- Choose your device (Pi 3 / 4 / 5).
- Choose OS: Raspberry Pi OS Lite (64-bit). The Lite image has no desktop, which is what you want.
- Choose your SD card.
- Click Next, then Edit Settings (the cog icon). Configure before flashing:
- Hostname: something memorable like
dnspi. - Enable SSH and paste your public key. Skip password auth.
- Username: pick whatever, e.g.
pi. - Skip WiFi configuration. This box runs on Ethernet.
- Set your locale and timezone.
- Hostname: something memorable like
- Write and let verification finish.
Slot the card into the Pi, plug in the Ethernet, power on.
Step 2: Find the Pi and SSH in
The Pi will pick up a DHCP address. Find it from your router’s DHCP lease list, or from your desktop:
# Adjust the subnet to match your LAN
nmap -sn 192.168.1.0/24 | grep -B2 -i raspberry
SSH in:
ssh pi@<pi-dhcp-ip>
If the first connection hangs for a minute, the Pi may still be expanding the filesystem on first boot. Give it two minutes.
Step 3: Set a static IP
A DNS server with a moving IP is a DNS server that breaks every time the lease rotates. Pin it.
Recent Raspberry Pi OS releases (Bookworm and Trixie) use NetworkManager. Older images (Bullseye and earlier) use dhcpcd. Check which one is running:
systemctl is-active NetworkManager
NetworkManager (Bookworm, Trixie, current default)
Find the connection name, then modify it. The default name is usually Wired connection 1 or netplan-eth0:
nmcli con show
# Replace the connection name and IPs to match your LAN
sudo nmcli con mod "Wired connection 1" \
ipv4.addresses 192.168.1.50/24 \
ipv4.gateway 192.168.1.1 \
ipv4.dns 9.9.9.9 \
ipv4.method manual
sudo nmcli con up "Wired connection 1"
dhcpcd (older images)
Edit /etc/dhcpcd.conf and add at the bottom:
interface eth0
static ip_address=192.168.1.50/24
static routers=192.168.1.1
static domain_name_servers=9.9.9.9
Then sudo systemctl restart dhcpcd.
Either way, verify and reconnect:
ip addr show eth0
ping -c 3 1.1.1.1
Reconnect SSH on the new static IP. From here on, this is “the Pi’s IP”.
Step 4: Install Blocky
Blocky ships as a single binary. No package manager involved.
Check the latest release at github.com/0xERR0R/blocky/releases and set the version. As of this guide’s verification date, the latest is v0.29.0.
BLOCKY_VERSION="v0.29.0" # check the releases page for the current tag
ARCH="arm64" # Pi 3/4/5 with the 64-bit OS
curl -L -o /tmp/blocky.tar.gz \
"https://github.com/0xERR0R/blocky/releases/download/${BLOCKY_VERSION}/blocky_${BLOCKY_VERSION}_Linux_${ARCH}.tar.gz"
tar -xzf /tmp/blocky.tar.gz -C /tmp
sudo mv /tmp/blocky /usr/local/bin/blocky
sudo chmod +x /usr/local/bin/blocky
blocky version
Create the config directory:
sudo mkdir -p /etc/blocky
Step 5: Write the config
sudo nano /etc/blocky/config.yml
A reasonable starter config. Comments inline so each block is obvious:
# Upstream resolvers. Blocky speaks DoH and DoT natively.
# Pick resolvers you trust. Common options: Quad9, Mullvad,
# Cloudflare 1.1.1.1, NextDNS, your VPN provider's resolver.
upstreams:
groups:
default:
# Quad9: Swiss-based, DNSSEC-validating, no logging
- https://dns10.quad9.net/dns-query
# Cloudflare: fast, widely available
- https://1.1.1.1/dns-query
# Plain DNS used only at startup, to resolve the DoH hostnames
# above before Blocky itself is ready to answer queries.
bootstrapDns:
- 9.9.9.9
- 1.1.1.1
blocking:
# One or more lists of domains to block. Hosts-file format
# works; so do plain domain lists. StevenBlack's hosts file
# is the standard starting point.
denylists:
ads:
- https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
# Domains to never block, even if a denylist contains them.
# Add entries when something legitimate breaks. The pipe
# syntax lets you list domains inline.
allowlists:
ads:
- |
example-payment-provider.com
# Apply the "ads" group to all clients.
clientGroupsBlock:
default:
- ads
# Refresh blocklists every 4 hours. Cached locally so a
# missing upstream at boot doesn't take blocking down.
loading:
refreshPeriod: 4h
downloads:
timeout: 60s
cooldown: 2s
maxErrorsPerSource: 5
ports:
dns: 53
http: 4000 # API and Prometheus metrics
log:
level: info
format: text
# Optional: Prometheus metrics at http://<pi-ip>:4000/metrics
prometheus:
enable: true
path: /metrics
Replace example-payment-provider.com with a domain you actually need to allow, or remove the allowlist block entirely if nothing breaks. You can come back to it.
Step 6: Run Blocky as a service
sudo nano /etc/systemd/system/blocky.service
[Unit]
Description=Blocky DNS
After=network-online.target
Wants=network-online.target
[Service]
ExecStart=/usr/local/bin/blocky --config /etc/blocky/config.yml
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
By default this runs as root, which is what lets it bind port 53. If you’d rather run as an unprivileged user, grant the binary the capability to bind low ports and add a User= line:
sudo setcap 'cap_net_bind_service=+ep' /usr/local/bin/blocky
# Then add: User=pi under [Service] in the unit file
Enable and start:
sudo systemctl daemon-reload
sudo systemctl enable blocky
sudo systemctl start blocky
sudo systemctl status blocky
If the unit fails immediately, check the pitfalls section below.
Step 7: Test before changing the network
Test against the Pi directly from your desktop, before changing anything on the router. This way, if something is wrong, only your single test query fails, not every device in the house.
# Replace 192.168.1.50 with the Pi's IP
# Should be blocked (returns 0.0.0.0)
nslookup doubleclick.net 192.168.1.50
# Should resolve normally
nslookup github.com 192.168.1.50
# If you added an allowlist entry, it should resolve
nslookup example-payment-provider.com 192.168.1.50
Tail the live log on the Pi while you test:
sudo journalctl -u blocky -f
You should see each query land, with blocked domains marked accordingly.
Step 8: Point the network at the Pi
Once Blocky answers correctly, change your router’s DHCP DNS setting to the Pi’s IP.
The exact path depends on your router. Common locations:
- LAN settings or DHCP server section.
- A field labelled DNS server, Primary DNS, or DNS Name Server.
- On UniFi: Settings then Networks, edit each network, DHCP Name Server.
- On a typical ISP router: LAN then DHCP, look for a “DNS override” toggle.
Set the primary DNS to the Pi’s IP. Save. If your router lets you set a secondary DNS, leave it blank (see the pitfalls section).
Devices pick up the new DNS on their next DHCP lease renewal. To force it on a desktop:
# Linux
sudo dhclient -r && sudo dhclient
# macOS
sudo ipconfig set en0 BOOTP && sudo ipconfig set en0 DHCP
# Or just toggle WiFi off and on
On Linux, confirm with resolvectl status | grep "DNS Servers". The Pi’s IP should appear.
Ongoing maintenance
# Recent queries
sudo journalctl -u blocky --since "1 hour ago"
# Restart after config changes
sudo systemctl restart blocky
# Force a blocklist refresh without restarting
curl -X POST http://localhost:4000/api/lists/refresh
To update Blocky, repeat Step 4 with a new BLOCKY_VERSION, then sudo systemctl restart blocky. Read the release notes first; config keys occasionally change between minor versions.
Optional: query log to a file
If you want a record of queries without setting up Prometheus and Grafana, Blocky can write a CSV:
# Add to /etc/blocky/config.yml
queryLog:
type: csv
target: /var/log/blocky/
logRetentionDays: 7
sudo mkdir -p /var/log/blocky
sudo chown pi:pi /var/log/blocky # match the user Blocky runs as
sudo systemctl restart blocky
A week of CSV is enough to spot a noisy device or a dead blocklist without filling the SD card.
# VERIFICATION
- From a device using the Pi as DNS, nslookup doubleclick.net returns 0.0.0.0 (blocked).
- nslookup github.com resolves normally.
- sudo journalctl -u blocky -f shows queries arriving from your desktop's IP.
- Optional: curl http://<pi-ip>:4000/metrics | grep blocky_query reports activity if Prometheus is enabled.
# COMMON PITFALLS
-
Port 53 already in use
systemd-resolved listens on 53 by default on some images. systemctl status blocky shows "bind: address already in use". Fix: edit /etc/systemd/resolved.conf, set DNSStubListener=no, then sudo systemctl restart systemd-resolved && sudo systemctl restart blocky. If systemd-resolved isn't strictly needed, sudo systemctl disable --now systemd-resolved.
-
Router only accepts one DNS
Set the Pi as primary and leave secondary blank. A "fallback DNS" is "silently bypass your blocklist whenever the primary is 5 ms slow", which is most of the time. Better to have DNS go down loudly for an hour than to have ads come back without you noticing.
-
Allowlist entry not taking effect
Blocky reloads denylists and allowlists on the schedule in loading.refreshPeriod, but config changes need a full restart: sudo systemctl restart blocky. Also check that the allowlist domain is in the same group name as the denylist (both under ads: in the starter config).
-
SD card died
It happens. SD cards are not great long-term storage for a DNS box that writes logs constantly. Keep /etc/blocky/config.yml checked into a git repo somewhere so a reflash takes thirty minutes, not three hours.
-
A specific app or device can't reach the internet
Some smart TVs and game consoles hardcode 8.8.8.8 and bypass your DNS entirely. Not a Blocky problem, but worth knowing. To force them through Blocky, configure your router to NAT outbound port 53 traffic back to the Pi.
# ROLLBACK
If you want to undo this:
- In the router, change the DHCP DNS back to whatever it was before. If you don't know, 1.1.1.1 (Cloudflare) or your ISP-provided value will work.
- On the Pi: sudo systemctl stop blocky && sudo systemctl disable blocky.
- Force a DHCP renew on a device and confirm DNS now points elsewhere with resolvectl status (or your platform's equivalent).
The Pi keeps running but does nothing on the network. Wipe the SD card whenever you're done with it.
For a less-drastic rollback, leave the Pi running and just point the router's DHCP DNS elsewhere. You can flip back in two minutes.
# REFERENCES
- Blocky on GitHub github.com
- Blocky configuration reference 0xerr0r.github.io
- Quad9 (Switzerland, DNSSEC, no logging) quad9.net
- Mullvad DNS (Sweden, privacy-first) mullvad.net
- Cloudflare 1.1.1.1 1.1.1.1
- StevenBlack hosts (the standard blocklist starting point) github.com
Comments (0)
No comments yet. Be the first.