Ads are annoying, but what bothers me more is the tracking behind them. Every website loads dozens of tracker domains that build a profile of your browsing habits - what you read, what you shop for, what interests you. That profile follows you around the web as targeted ads.

Browser extensions help, but they only work on that one browser. My phone, smart TV, and other devices still load all those trackers.

A Raspberry Pi running Pi-hole blocks tracker domains at the DNS level - before they reach any device on your network. I built finite to make this setup reproducible.

How DNS-Level Blocking Works

When you visit a website, your browser makes DNS requests for every domain that page needs - the main site, plus all the ad networks and analytics services embedded in it.

Pi-hole sits between your devices and the internet. When a request comes in for a known tracker domain, Pi-hole returns a blank response. The tracker never loads.

This happens before any connection is made, so it’s fast and works for every device on your network automatically.

Why Not Just Use Browser Extensions?

Extensions like uBlock Origin are great, but they have limitations:

  • Per-device, per-browser. You need to install them everywhere.
  • Mobile is tricky. iOS Safari doesn’t support content blockers the same way. Android varies by browser.
  • Smart devices have no browser. Your TV, game console, and IoT devices load trackers too.

DNS-level blocking works at the network layer. One Pi covers everything connected to your WiFi.

The Setup: Pi-hole + Unbound

My setup uses two components:

  • Pi-hole - Maintains blocklists and filters DNS requests
  • Unbound - A local DNS resolver that caches queries

The caching is a nice bonus. Frequently accessed domains resolve instantly from local cache instead of round-tripping to external servers.

Speed in Practice

Here’s a first query going upstream to resolve github.com:

$ dig github.com @192.168.50.2 | grep "Query time"
;; Query time: 51 msec

Same query again, now served from Unbound’s cache:

$ dig github.com @192.168.50.2 | grep "Query time"
;; Query time: 5 msec

10x faster. For domains you hit frequently, this adds up.

And here’s what happens when you query a tracker domain:

$ dig analytics.tiktok.com @192.168.50.2
 
;; ANSWER SECTION:
analytics.tiktok.com.    2    IN    A    0.0.0.0
 
;; Query time: 8 msec

Pi-hole returns 0.0.0.0 - the request never leaves your network. The tracker doesn’t load, and your browser moves on.

What Gets Blocked

Pi-hole uses community-maintained blocklists. Out of the box, it blocks:

  • Ad networks and ad-serving domains
  • Third-party analytics services
  • Known tracking domains
  • Some malware domains

You can add or remove lists based on your needs. Too aggressive? Whitelist specific domains. Want more coverage? Add additional blocklists.

The Trade-offs

Running your own DNS has some considerations:

Initial setup. You need a Raspberry Pi, an SD card, and some time to configure your router. With finite, the NixOS config handles most complexity, but it’s still a project.

Maintenance. The Pi needs to stay running. If it goes down, your network loses DNS until you fix it or point devices elsewhere.

Some things break. Occasionally a site or app doesn’t work because a required domain is on a blocklist. Pi-hole’s query log makes it easy to find and whitelist these.

Not a silver bullet. DNS blocking doesn’t stop first-party tracking (analytics on the same domain as the site) or tracking via IP addresses. It’s one layer of defense, not complete protection.

Results

After running this setup for a few months:

  • About 15-20% of DNS queries on my network are to tracker domains that get blocked
  • Page loads feel snappier, especially on ad-heavy sites
  • My smart TV stopped phoning home to dozens of telemetry domains
  • No more “that ad is following me” moments

Getting Started

I packaged my setup as finite - a NixOS configuration for Raspberry Pi that includes Pi-hole and Unbound pre-configured.

If you want to try it:

Next in this series: Why NixOS for Raspberry Pi - declarative configuration means the setup is reproducible and version-controlled.