Skip to content
Go back

Unbound vs Technitium vs BIND

By SumGuy 11 min read
Unbound vs Technitium vs BIND

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

FeatureUnboundTechnitiumBIND9
Recursive resolverYesYesYes
Authoritative serverNoYesYes
Web GUINoYesNo
DNSSEC validationYes (default)YesYes (manual config)
DoH / DoT / DoQVia proxy onlyNativeVia proxy only
Block listsVia Pi-hole/AdGuardNativeVia RPZ
RAM usage~10 MB~80–150 MB~50–100 MB
Config styleunbound.confWeb UI + APInamed.conf
Best forMinimal + Pi-holeAll-in-oneAuthoritative 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

Terminal window
# Debian/Ubuntu
apt install unbound
# Alpine (containers, LXC)
apk add unbound
# Docker
docker run --rm -d \
-p 53:53/udp \
-p 53:53/tcp \
-v /etc/unbound:/etc/unbound \
mvance/unbound:latest

Unbound ships with a sane default config that validates DNSSEC out of the box. You mostly just need to tune it.

The Config

unbound.conf
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-horizon
local-zone: "home.lab." static
local-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

Terminal window
# In Pi-hole web UI: Settings → DNS
# Uncheck all upstream providers
# Add custom upstream: 127.0.0.1#5335
# Verify DNSSEC is working
dig 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 record

What 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

Terminal window
# Debian/Ubuntu — official install script
curl -sSL https://download.technitium.com/dns/install.sh | sudo bash
# Docker
docker 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:latest

After 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

Terminal window
# Technitium has a full REST API
# Get server stats
curl -s "http://localhost:5380/api/dashboard/stats/get?token=YOUR_TOKEN"
# Add a DNS record via API
curl -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 lists
curl -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

Terminal window
# Debian/Ubuntu
apt install bind9 bind9-utils dnsutils
# Check version
named -v
# BIND 9.18.x (Extended Support Version)

Basic Recursive Config

named.conf
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.

named.conf
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.

named.conf
options {
// ... other options ...
response-policy {
zone "rpz.block";
};
};
zone "rpz.block" {
type master;
file "/etc/bind/rpz/rpz.block.db";
allow-query { none; };
};
/etc/bind/rpz/rpz.block.db
$TTL 1H
@ SOA LOCALHOST. root.LOCALHOST. (1 1h 15m 30d 2h)
NS LOCALHOST.
; Block these domains
ads.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.

Terminal window
# Validate config before reloading
named-checkconf /etc/named.conf
# Check zone file
named-checkzone home.lab /etc/bind/zones/db.home.lab
# Reload without restart
rndc reload
# Flush cache
rndc flush
# Check stats
rndc stats && cat /var/cache/bind/named_stats.txt

DNSSEC, 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

ProtocolUnboundTechnitiumBIND
DNS-over-TLS (DoT)Via proxy (nginx/caddy)Native, port 853Via proxy
DNS-over-HTTPS (DoH)Via proxyNative, port 443Via proxy
DNS-over-QUIC (DoQ)NoNative, port 853 UDPNo

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

Terminal window
# After running each under normal home lab query load (~500 q/s)
# Unbound
ps aux | grep unbound
# ~8–12 MB RSS, <0.5% CPU
# Technitium
docker stats technitium-dns --no-stream
# ~95–140 MB RSS, ~1–2% CPU baseline
# BIND9
ps aux | grep named
# ~55–90 MB RSS, <1% CPU

On 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:

Pick Technitium if:

Pick BIND9 if:

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.


Share this post on:

Send a Webmention

Written about this post on your own site? Send a webmention and it'll show up above once verified.


Previous Post
Immich vs PhotoPrism vs Ente: Self-Hosted Photo Libraries
Next Post
Function Calling in Local LLMs

Discussion

Powered by Garrul . Sign in with GitHub or Google, or post anonymously.

Related Posts