My private internet: Pi-hole, Unbound & Tailscale

Wed, April 1, 2026 - 10 min read
Diagram showing DNS query flow through Unbound, Pi-hole and Tailscale

I’d been meaning to do this for a while. A browser extension handles ads on my laptop fine, but it does nothing for my phone, my smart TV, or the rest of the devices on my network that quietly phone home whenever they feel like it. DNS-level blocking solves all of that in one place — before any device even makes the connection.

The other thing worth mentioning: this isn’t just for your own devices. Once Pi-hole is the DNS server for your home network, every device in the house benefits — your partner’s phone, the kids’ tablets, the guest WiFi, all of it. Nobody has to install anything. It just works for everyone on the network, including people who’ve never heard of the setup and don’t need to.

The stack I landed on: Pi-hole to filter out ad and tracker domains, Unbound as a recursive resolver so queries don’t pass through a third-party DNS provider, and Tailscale to make the whole thing work on every device I own, wherever I am. Runs on a small Linux box behind the router, took an afternoon to set up, and I haven’t needed to touch it since.


How the pieces fit together

The three components look similar on the surface but solve very different problems. It helps to know what each one actually does before you start, or the Tailscale configuration steps won’t make much sense.

Pi-hole sits at the front and acts as a filter. It doesn’t resolve anything itself — it checks every DNS query against its blocklists and kills the bad ones before they go anywhere. Ad domain? The request returns nothing and dies there.

Unbound does the actual DNS resolution, but recursively — starting from the root DNS servers and following the chain all the way to the answer. No Cloudflare, no Google, no third party in between.

Tailscale wraps the whole thing in a private WireGuard mesh and makes Pi-hole reachable from every device you own, no matter where you are. It then tells all your devices to use your Pi-hole as their DNS server — automatically, without configuring each one individually.

Three components, one job each, no overlap.


What you need

A Linux host. Any always-on machine works: a Raspberry Pi (any model with 512 MB RAM or more), a spare mini-PC sitting in a drawer, a VM on a home server, or a cheap VPS. DNS is not a heavy workload — even a single-core ARM chip handles a full household without breaking a sweat. I use a Raspberry Pi and it sits at maybe 3% CPU on a busy day.

Debian or Ubuntu. All commands below assume this. Raspberry Pi OS is Debian-based, so it fits perfectly.

A static LAN IP. Either set it on the device itself or create a DHCP reservation on your router. A DNS server with a changing address is quietly catastrophic — things will just randomly stop working and you’ll spend 20 minutes confused before you figure out why.

A Tailscale account. Free for personal use, up to 100 devices. No credit card required.


Step 1 — Install Pi-hole

curl -sSL https://install.pi-hole.net | bash

The interactive installer handles most of the configuration. A few things to pay attention to:

  • Interface: pick your LAN interface (eth0 or wlan0). You can revisit this after Tailscale is installed.
  • Upstream DNS: pick anything for now — Cloudflare, Google, whatever. You will replace it with Unbound in Step 3, so it doesn’t matter yet.
  • Static IP: confirm it here if you haven’t already set it at the router.

The installer will print a web interface password at the end. Note it down. If you miss it:

pihole setpassword

Once installed, check that the admin panel loads at http://<your-host-ip>/ (Pi-hole v6 serves the panel at the root; /admin still redirects there). If it does, Pi-hole is running.


Step 2 — Install and configure Unbound

This is the step most tutorials skip, and it’s the one I find most satisfying. With Unbound in place, your DNS queries don’t go anywhere externally at all. Unbound walks the DNS tree itself — root servers, TLD servers, authoritative servers — and hands the answer back to Pi-hole. Your ISP and every DNS provider out there are completely out of the picture.

The trade-off is that the very first query to a domain you’ve never visited can be slightly slower, since Unbound has to traverse the full chain from scratch. Subsequent queries are cached and fast. In practice I’ve never noticed the difference.

Install

sudo apt install unbound -y

Download root hints

Root hints are a list of the DNS root servers. Unbound ships with built-in defaults, but keeping a fresh copy is good practice.

wget -O root.hints https://www.internic.net/domain/named.root
sudo mv root.hints /var/lib/unbound/

While you’re at it, add a monthly cron job so it stays current:

sudo crontab -e
# add this line:
0 3 1 * * wget -qO /var/lib/unbound/root.hints https://www.internic.net/domain/named.root && systemctl restart unbound

Configure

Create the config file:

sudo nano /etc/unbound/unbound.conf.d/pi-hole.conf

Paste this in:

server:
    verbosity: 0
 
    interface: 127.0.0.1
    port: 5335
 
    do-ip4: yes
    do-udp: yes
    do-tcp: yes
    do-ip6: no
    prefer-ip6: no
 
    root-hints: "/var/lib/unbound/root.hints"
 
    # DNSSEC hardening
    harden-glue: yes
    harden-dnssec-stripped: yes
    harden-large-queries: yes
 
    use-caps-for-id: no       # leave off — known to cause DNSSEC issues
    edns-buffer-size: 1232
 
    # Caching
    cache-min-ttl: 300
    cache-max-ttl: 86400
    prefetch: yes
    prefetch-key: yes
 
    # Performance — single thread is plenty for home use
    num-threads: 1
    msg-cache-size: 50m
    rrset-cache-size: 100m
    so-reuseport: yes
    so-rcvbuf: 4m
    so-sndbuf: 4m
 
    # Never leak private address ranges to public DNS
    private-address: 192.168.0.0/16
    private-address: 172.16.0.0/12
    private-address: 10.0.0.0/8
    private-address: 169.254.0.0/16

Debian Bullseye+ quirk

If you’re on Bullseye or newer (which includes recent Raspberry Pi OS), the system auto-installs openresolv with a configuration that quietly conflicts with Unbound. Disable it before restarting:

sudo systemctl disable --now unbound-resolvconf.service
sudo systemctl restart unbound

Skip this step and things will seem fine until they mysteriously aren’t.

Verify it’s working

# Should return a valid IP address
dig github.com @127.0.0.1 -p 5335
 
# DNSSEC check — should return SERVFAIL (bad signature correctly rejected)
dig sigfail.verteiltesysteme.net @127.0.0.1 -p 5335
 
# DNSSEC check — should return NOERROR with 'ad' in the flags
dig sigok.verteiltesysteme.net @127.0.0.1 -p 5335

That ad flag in the last response stands for “Authentic Data” — DNSSEC validation is working. If both DNSSEC checks pass, Unbound is doing exactly what it should.


Step 3 — Point Pi-hole at Unbound

Now wire the two together. Open Pi-hole admin → Settings → DNS.

  1. Uncheck every upstream DNS provider in both columns.
  2. Check Custom 1 (IPv4) and enter 127.0.0.1#5335.
  3. In Advanced DNS settings, enable:
    • Never forward reverse lookups for private IP ranges
    • Never forward non-FQDNs
  4. Disable Use DNSSEC — Unbound already handles this. Leaving it on in Pi-hole too causes duplicate validation and weird failures.
  5. Save & Apply.

Load a few websites from a device on your LAN. Pi-hole’s query log should start filling up. You’re now running a fully self-contained DNS stack.


Step 4 — Install Tailscale

Pi-hole on its own only works at home. Tailscale is what makes it global. It creates a private WireGuard mesh between all your devices, and once you tell it to use your Pi-hole as the DNS server, every device on that mesh gets ad-blocking everywhere — at the office, in a café, on mobile data in another country.

curl -fsSL https://tailscale.com/install.sh | sh
 
# --accept-dns=false is important here.
# This machine IS the DNS server — you don't want Tailscale
# overwriting its own DNS configuration.
sudo tailscale up --accept-dns=false

Authenticate via the URL printed in the terminal. Your host should appear in the Tailscale admin console. Take note of the Tailscale IP assigned to it — a 100.x.x.x address. You need that in the next step, not the LAN IP.


Step 5 — Let Pi-hole hear Tailscale traffic

By default, Pi-hole only responds to DNS queries from interfaces it considers “local”. Tailscale traffic arrives on tailscale0, which Pi-hole doesn’t recognise as local — so it silently ignores those queries.

In Pi-hole admin → Settings → DNS, scroll down to the Interface settings section (Pi-hole v6 shows all settings by default — there is no Expert mode toggle), then set it to Permit all origins.

One security note: this is safe as long as port 53 on this machine isn’t exposed to the public internet. On a home device behind NAT, that’s true by default. If you’re running this on a VPS with a public IP, block port 53 for all traffic except the tailscale0 interface — otherwise you’ll accidentally run a public open resolver, which is a bad time for everyone.


Step 6 — Tell Tailscale to use your Pi-hole

Open the Tailscale admin console and go to DNS.

  1. Under Global nameservers, click Add nameserver → Custom.
  2. Enter the Pi-hole’s Tailscale IP (100.x.x.x) — not the LAN IP.
  3. Enable Override local DNS.
  4. Optionally enable MagicDNS if you want hostname resolution within your tailnet.

Toggle Tailscale off and back on on one of your other devices to force a DNS refresh. Open Pi-hole’s query log — you should see queries rolling in from tailnet IPs. Try browsing something ad-heavy on your phone with WiFi off. Silence.


Optional — Exit node for full VPN mode

By default Tailscale only routes DNS through your home. Everything else goes directly to the internet from wherever you are. For most situations that’s ideal — you get the ad-blocking without any performance overhead.

If you’re on a sketchy hotel network and want all traffic routed home, you can advertise your Linux box as an exit node:

sudo tailscale up --advertise-exit-node --accept-dns=false

Approve it in the Tailscale admin console. On each client you can toggle exit node routing on or off per connection. Off by default, so you only pay the latency cost when you actually want it.


What this won’t fix

Worth being honest. The setup is genuinely great but it has real limits.

ScenarioBlocked?
Third-party ad networksâś… Yes
Tracker & telemetry domains (apps, smart TVs, IoT)âś… Yes
YouTube ads❌ No — served from the same domains as the video
Ads on same-domain pages (Facebook, etc.)❌ No
Devices not on Tailscale, away from home❌ No
Everything, when the Pi-hole host is offline❌ DNS breaks

YouTube is the one that still stings. Google serves ads and content from the same domains, so there’s no DNS trick that kills the ads without also killing the video. A browser extension is still the right answer there.

The offline scenario is worth planning for. If your host goes down, all tailnet devices lose DNS resolution. Add a fallback in Tailscale’s global nameservers — 1.1.1.1 as a secondary works fine — so devices gracefully degrade to working internet with ads, rather than no internet at all.


Keeping it running

Low-maintenance is an understatement. A few commands every month or so is genuinely all it takes:

# Update Pi-hole
pihole -up
 
# Refresh blocklists
pihole -g
 
# Update Unbound root hints (if you skipped the cron job)
wget -qO /var/lib/unbound/root.hints https://www.internic.net/domain/named.root
sudo systemctl restart unbound
 
# General system updates
sudo apt update && sudo apt upgrade -y

For blocklists beyond Pi-hole’s defaults, hagezi/dns-blocklists is the best I’ve found — actively maintained, multiple tiers from light to aggressive, and it actually documents what each list breaks so you can make an informed call.


The full picture

ComponentDoes what
Linux host (Pi, VM, VPS…)Always-on platform
Pi-holeDNS sinkhole — blocks ads and trackers
UnboundRecursive resolver — no third party involved
TailscaleWireGuard mesh — extends the whole stack everywhere

An afternoon of setup. Months of running without touching it. No ads on my phone on the train, no overnight telemetry leaking off the TV, no DNS provider logging my queries.

The moment that really sold me on adding Unbound was opening Pi-hole’s query log for the first time and watching my smart TV make 340 DNS requests in a single evening — almost all of them to telemetry and ad endpoints. Every one hit a wall. No browser extension was ever going to catch that.


References