Your ISP Knows What You’re Watching
Every DNS query you fire off to your ISP’s resolver is a little postcard that says “hey, I’m thinking about this domain at 1:47 AM.” They collect those postcards. They sell them. Your ISP DNS server knows your binge-watching habits better than your streaming service does.
So you stand up Pi-hole or AdGuard Home. Great move. But now what are they using as their upstream? By default, a lot of guides tell you to just forward to 1.1.1.1 or 8.8.8.8. You’ve traded one data broker for another.
The right answer is a validating recursive resolver — something that walks the DNS tree itself, starting from root servers, validating DNSSEC signatures along the way, and not phoning home to anyone. Your home lab gets faster local DNS, DNSSEC validation, and nobody upstream logging your queries.
You’ve got three serious contenders: Unbound, Technitium DNS, and BIND9. Let’s break them down.
The Contenders at a Glance
| Feature | Unbound | Technitium | BIND9 |
|---|---|---|---|
| Recursive resolver | Yes | Yes | Yes |
| Authoritative server | No | Yes | Yes |
| Web GUI | No | Yes | No |
| DNSSEC validation | Yes (default) | Yes | Yes (manual config) |
| DoH / DoT / DoQ | Via proxy only | Native | Via proxy only |
| Block lists | Via Pi-hole/AdGuard | Native | Via RPZ |
| RAM usage | ~10 MB | ~80–150 MB | ~50–100 MB |
| Config style | unbound.conf | Web UI + API | named.conf |
| Best for | Minimal + Pi-hole | All-in-one | Authoritative zones |
Unbound: The Minimal, Trust-Nobody Resolver
Unbound is what the OpenBSD and FreeBSD crowd reaches for by default. It’s a validating, recursive, caching resolver. It does one job extremely well and doesn’t try to be anything else.
Install
# Debian/Ubuntuapt install unbound
# Alpine (containers, LXC)apk add unbound
# Dockerdocker run --rm -d \ -p 53:53/udp \ -p 53:53/tcp \ -v /etc/unbound:/etc/unbound \ mvance/unbound:latestUnbound ships with a sane default config that validates DNSSEC out of the box. You mostly just need to tune it.
The Config
server: verbosity: 1 interface: 0.0.0.0 port: 5335 do-ip4: yes do-udp: yes do-tcp: yes do-ip6: no
# DNSSEC validation auto-trust-anchor-file: "/var/lib/unbound/root.key"
# Cache tuning cache-min-ttl: 3600 cache-max-ttl: 86400 cache-max-negative-ttl: 600 rrset-cache-size: 256m msg-cache-size: 128m num-threads: 2
# Privacy hardening hide-identity: yes hide-version: yes qname-minimisation: yes use-caps-for-id: yes
# Refuse recursion to everything except localhost and LAN access-control: 127.0.0.0/8 allow access-control: 192.168.0.0/16 allow access-control: 10.0.0.0/8 allow access-control: 0.0.0.0/0 refuse
# Root hints — update annually or let unbound-anchor do it root-hints: "/var/lib/unbound/root.hints"
# Prefetch popular records prefetch: yes prefetch-key: yes
# Local zone for split-horizonlocal-zone: "home.lab." staticlocal-data: "pihole.home.lab. IN A 192.168.1.10"local-data: "nas.home.lab. IN A 192.168.1.20"local-data: "proxmox.home.lab. IN A 192.168.1.30"Notice the port is 5335 here — the classic pattern when you run Pi-hole in front of Unbound. Pi-hole listens on 53, forwards to Unbound on 5335. You get Pi-hole’s block lists plus Unbound’s recursive resolution and DNSSEC.
Pi-hole + Unbound: The Classic Combo
# In Pi-hole web UI: Settings → DNS# Uncheck all upstream providers# Add custom upstream: 127.0.0.1#5335
# Verify DNSSEC is workingdig sigfail.verteiltesysteme.net @127.0.0.1 -p 5335# Should return SERVFAIL (expected — it's a DNSSEC failure test)
dig sigok.verteiltesysteme.net @127.0.0.1 -p 5335# Should return NOERROR with a valid A recordWhat Unbound Can’t Do
Unbound is recursive-only by design. It will not serve authoritative zones to the outside world. It won’t do DoH or DoT natively — you’d need a Nginx or Caddy proxy in front for that. It has no web interface. If that bothers you, keep reading.
Technitium DNS: The “Just Give Me a Dashboard” Option
Technitium DNS Server is a newer entrant built on .NET. It does recursive resolution, authoritative serving, DNS-over-HTTPS, DNS-over-TLS, DNS-over-QUIC, built-in blocking, zone management, conditional forwarding, split-horizon — all from a web UI. It’s the DNS equivalent of “yes, and.”
The .NET runtime adds overhead (~80–150 MB baseline vs Unbound’s ~10 MB), but on any modern home lab hardware that’s a rounding error.
Install
# Debian/Ubuntu — official install scriptcurl -sSL https://download.technitium.com/dns/install.sh | sudo bash
# Dockerdocker run -d \ --name technitium-dns \ -p 53:53/udp \ -p 53:53/tcp \ -p 5380:5380/tcp \ -p 853:853/tcp \ -p 443:443/tcp \ -v technitium_data:/etc/dns \ technitium/dns-server:latestAfter install, web UI is at http://your-server:5380. Default credentials: admin / admin (change this immediately, obviously).
What the Config Looks Like
There’s no technitium.conf you edit by hand. Everything lives in the web UI or the HTTP API. But here’s roughly what you’d configure:
Recursion settings: General → Enable Recursion → Yes, with forwarder fallback disabled so it always recurses from root.
DNSSEC: General → DNSSEC Validation → Enabled. Done. No trust-anchor-file to manage.
Block lists: Go to Blocking → Configure Block List URLs. Paste in your Hagezi, StevenBlack, OISD, or any URL pointing to a hosts-format list. Technitium polls and refreshes them on a schedule. No Pi-hole required.
Split-horizon / internal zones: Zones → Add Zone → type home.lab, select Primary Zone. Add A records via the UI. Technitium handles the rest.
DoH endpoint: Once you point a domain at the server and set up a TLS cert, you get https://dns.yourdomain.com/dns-query for free. No proxy needed.
API Example
# Technitium has a full REST API# Get server statscurl -s "http://localhost:5380/api/dashboard/stats/get?token=YOUR_TOKEN"
# Add a DNS record via APIcurl -s "http://localhost:5380/api/zones/records/add?token=YOUR_TOKEN&domain=nas.home.lab&zone=home.lab&type=A&ttl=3600&ipAddress=192.168.1.20"
# Refresh all block listscurl -s "http://localhost:5380/api/settings/blockingLists/forceUpdate?token=YOUR_TOKEN"Get your API token from the web UI under Admin → API Token.
Performance and Observability
Technitium exposes a Prometheus-compatible metrics endpoint at /api/dashboard/stats/get. The dashboard shows query rate, cache hit ratio, DNSSEC stats, and top blocked domains in real time. It’s genuinely nice for a home lab setup where you want to actually see what’s happening.
Cache size is configurable in General settings. Default is 10,000 entries. Bump it to 100,000+ on any hardware that’s not a Raspberry Pi Zero.
Technitium’s Weakness
The .NET dependency is real. On ARM64 (Pi 4, Raspberry Pi 5, etc.) it works fine but the startup time is noticeable. On very constrained hardware (256 MB RAM, 1 CPU), stick with Unbound. Also: the logging UI is powerful but verbose — tune your log level or you’ll fill disks faster than expected. And while the web UI is great for day-to-day ops, it’s harder to manage as code compared to a flat config file you can keep in git.
BIND9: The One Your ISP Ran in 2003
BIND (Berkeley Internet Name Domain) has been around since the 80s. It runs the internet. It’s authoritative and recursive. It has zone transfers, TSIG, RPZ (Response Policy Zones — basically block lists), DNSSEC signing, views (split-horizon), and enough knobs to occupy an entire career.
Install
# Debian/Ubuntuapt install bind9 bind9-utils dnsutils
# Check versionnamed -v# BIND 9.18.x (Extended Support Version)Basic Recursive Config
options { directory "/var/cache/bind";
// Recursive resolver — do NOT forward recursion yes; allow-recursion { 127.0.0.0/8; 192.168.0.0/16; 10.0.0.0/8; };
// DNSSEC validation dnssec-validation auto;
// Listen on all interfaces listen-on { any; }; listen-on-v6 { none; };
// Privacy hardening version none; hostname none; server-id none;
// Cache tuning max-cache-size 256m; max-ncache-ttl 600;
// EDNS Client Subnet — disable to preserve privacy send-cookie no;};Split-Horizon with Views
This is where BIND genuinely shines over the others. Views let you return different answers based on who’s asking — internal clients get private IP records, external clients get public IPs.
view "internal" { match-clients { 192.168.0.0/16; 10.0.0.0/8; }; recursion yes;
zone "home.lab" { type master; file "/etc/bind/zones/db.home.lab"; };
zone "yourdomain.com" { type master; file "/etc/bind/zones/db.yourdomain.com.internal"; };};
view "external" { match-clients { any; }; recursion no;
zone "yourdomain.com" { type master; file "/etc/bind/zones/db.yourdomain.com.external"; };};Internal clients querying yourdomain.com get 192.168.1.x. External clients get your public IP. This is the use case BIND was born for.
RPZ: Block Lists the BIND Way
Response Policy Zones let BIND intercept and rewrite DNS responses — the operator-grade equivalent of Pi-hole’s block lists.
options { // ... other options ... response-policy { zone "rpz.block"; };};
zone "rpz.block" { type master; file "/etc/bind/rpz/rpz.block.db"; allow-query { none; };};$TTL 1H@ SOA LOCALHOST. root.LOCALHOST. (1 1h 15m 30d 2h) NS LOCALHOST.
; Block these domainsads.example.com CNAME .tracker.ads.net CNAME .RPZ is powerful but maintaining custom zone files by hand is tedious compared to Technitium’s block list UI. In practice, you’d sync from a curated source via script.
BIND’s Honest Overhead
BIND is heavier than Unbound for a pure recursive use case. It’s not heavy by modern standards — 50–100 MB RAM for a residential workload is fine — but you’re carrying authoritative serving machinery you may never use. The config is verbose and the documentation is sprawling. named-checkconf is your friend; run it religiously after any change.
# Validate config before reloadingnamed-checkconf /etc/named.conf
# Check zone filenamed-checkzone home.lab /etc/bind/zones/db.home.lab
# Reload without restartrndc reload
# Flush cacherndc flush
# Check statsrndc stats && cat /var/cache/bind/named_stats.txtDNSSEC, DoH/DoT: Who Actually Has It
DNSSEC Validation
All three validate DNSSEC. Unbound does it by default and fails closed (SERVFAIL on broken chains). BIND needs dnssec-validation auto; which is the default in 9.16+. Technitium enables it with one checkbox. Any of the three will protect you from cache poisoning attacks.
DoH / DoT / DoQ
| Protocol | Unbound | Technitium | BIND |
|---|---|---|---|
| DNS-over-TLS (DoT) | Via proxy (nginx/caddy) | Native, port 853 | Via proxy |
| DNS-over-HTTPS (DoH) | Via proxy | Native, port 443 | Via proxy |
| DNS-over-QUIC (DoQ) | No | Native, port 853 UDP | No |
If you want your mobile devices using DoH/DoT back to home via Tailscale or WireGuard, Technitium makes this trivial. The others require a reverse proxy in front.
Resource Usage Reality Check
# After running each under normal home lab query load (~500 q/s)
# Unboundps aux | grep unbound# ~8–12 MB RSS, <0.5% CPU
# Technitiumdocker stats technitium-dns --no-stream# ~95–140 MB RSS, ~1–2% CPU baseline
# BIND9ps aux | grep named# ~55–90 MB RSS, <1% CPUOn a Raspberry Pi 4 with 2 GB RAM, all three are fine. On a Pi Zero 2 W (512 MB), Unbound is your only real choice. On anything running Proxmox or a NAS, pick whatever fits your workflow — RAM isn’t the bottleneck.
The Verdict
Pick Unbound if:
- You already run Pi-hole or AdGuard Home for blocking and want a proper recursive upstream
- You want the absolute minimum footprint
- You’re comfortable editing a config file and don’t need a GUI
- You want DNSSEC without thinking about it
- Your split-horizon needs are simple (a handful of
local-dataentries)
Pick Technitium if:
- You want one service handling recursion, blocking, authoritative zones, DoH/DoT, and observability
- You hate maintaining Pi-hole + Unbound as two separate things
- You want a web UI with real-time stats and block list management
- You’re deploying on x86 hardware and don’t care about the .NET overhead
- DoQ support actually matters to you (it’s the future, and Technitium already has it)
Pick BIND9 if:
- You’re running an actual authoritative nameserver serving public or multi-site zones
- You need real split-horizon with
views— serving different answers to internal vs external clients - You have RPZ requirements from an enterprise or ISP background
- You already know BIND and don’t want to learn something new
For most home lab setups, the answer is either Unbound behind Pi-hole (proven, minimal, battle-tested) or Technitium standalone (modern, self-contained, GUI). BIND is the right call the moment you need to be the authoritative source for a real domain with zone transfers, secondary servers, or complex views logic.
Your ISP DNS is out. One of these three is in. Any of them is the right call compared to what you had before.