For a long time I ran Pi-hole as my network’s ad and malware sink. It’s a great project, but I’ve since replaced it with Technitium DNS Server, running on two machines so that one going down never takes the whole house offline.
What a DNS sinkhole does
Every time a device opens a website or an app phones home, it first asks a DNS server to turn a name like ads.example.com into an IP address. A DNS sinkhole checks each name against blocklists of known ad, tracking and malware domains. If the name is on a list, it simply doesn’t give a real answer, so the ad, tracker or malicious download never loads. This works for every device on the network, including smart TVs and phones where you can’t install an ad blocker.
Why I moved from Pi-hole to Technitium
Pi-hole does blocking brilliantly. Technitium does blocking too, but it’s also a complete DNS server, which suits a growing home lab:
- Encrypted upstream DNS built in: queries leave my network over DNS-over-HTTPS, with no extra resolver containers to bolt on.
- Wildcard blocklists: blocking a domain blocks all its subdomains, so modern lists stay smaller and more effective.
- Proper local zones: I can give my own machines and services real names on the home network.
- Apps: optional add-ons like a searchable query log stored in SQLite.
- A clean web interface with a dashboard of queries, blocked requests and top clients.
Why two servers?
When all your DNS goes through one box, that box becomes a single point of failure. Reboot it for updates, or have it crash, and every device in the house “loses the internet”, even though the connection is fine. So I run Technitium on two separate machines, and every device is given both as DNS 1 and DNS 2. Getting that last part right turned out to be trickier than expected, as step 6 explains.

Setting it up
1. Free up port 53 (Ubuntu)
Ubuntu’s built-in systemd-resolved already listens on port 53, which a DNS server needs. Turn off its stub listener:
sudo sed -i 's/^#\?DNSStubListener=.*/DNSStubListener=no/' /etc/systemd/resolved.conf
sudo systemctl restart systemd-resolved
Once Technitium is running, point the server at itself (127.0.0.1) and at your second DNS server, so it keeps working even if the local one is down.
2. The compose file
Save as /opt/stacks/technitium/compose.yaml, and put the admin password in a .env file rather than in the compose file:
services:
technitium:
container_name: technitium
image: technitium/dns-server:latest
network_mode: host
environment:
TZ: 'Europe/Dublin'
DNS_SERVER_DOMAIN: 'dns-server'
DNS_SERVER_ADMIN_PASSWORD: ${DNS_ADMIN_PASSWORD}
DNS_SERVER_LOG_USING_LOCAL_TIME: 'true'
DNS_SERVER_WEB_SERVICE_HTTP_PORT: '5380'
DNS_SERVER_WEB_SERVICE_LOCAL_ADDRESSES: '0.0.0.0'
DNS_SERVER_ENABLE_BLOCKING: 'true'
volumes:
- ./etc-technitium:/etc/dns
- ./logs:/var/log/technitium
cap_add:
- NET_BIND_SERVICE
restart: unless-stopped
echo "DNS_ADMIN_PASSWORD=pick-a-strong-password" > .env
chmod 600 .env
docker compose up -d
network_mode: host lets Technitium answer DNS on port 53 directly, and see real client IPs in its logs. The password variable only sets the admin password on first start. After that, change it in the web UI.
3. Encrypted upstream DNS
Open http://<server-ip>:5380 and log in. Under Settings → Proxy & Forwarders, use the quick-select to add Cloudflare and Google with the DNS-over-HTTPS protocol. I use both:
https://1.1.1.1/dns-query
https://8.8.8.8/dns-query
4. Add blocklists
Under Settings → Blocking, make sure blocking is enabled and add your blocklist URLs. These are the ones I use:
- HaGeZi Multi and HaGeZi Pro++: well-maintained lists covering ads, tracking, malware, phishing and scams, in wildcard format (github.com/hagezi/dns-blocklists).
- StevenBlack unified hosts with the fake-news and gambling extensions (github.com/StevenBlack/hosts). There’s also a variant that adds adult content, which is handy on a family network.
Tip Technitium downloads and refreshes the lists automatically. Start with one or two lists and add more gradually. Aggressive lists like Pro++ occasionally block something you need, which the Allowed tab fixes in seconds.
5. Build the second server
On the second machine, repeat steps 1 and 2. Then use Settings → Backup Settings on the first server and Restore Settings on the second to copy the forwarders, blocklists and allow-list across.
Keep the two in sync Devices don’t strictly use “DNS 1 first, DNS 2 as backup”. They often spread queries across both. If the servers have different blocklists, ads will mysteriously appear on some page loads and not others. Whenever you change a list on one, make the same change on the other.
6. Let Technitium hand out addresses
Devices learn their DNS servers from DHCP, the service that hands out IP addresses on your network. Usually that’s the router’s job, and that’s where I hit a snag. My FRITZ!Box can only announce one local DNS server. The obvious workaround is to point the router itself at both Technitium servers, but then every query arrives from the router’s address, so Technitium can’t tell your devices apart and its dashboard becomes one big anonymous blob.
The fix is to let Technitium run DHCP instead. It has a full DHCP server built in, it happily announces both DNS servers, and it registers every device’s name as it hands out an address. I run it on the Raspberry Pi, because that box also runs my mail server and is always on.
Before you switch Only run DHCP on one server, because two DHCP servers on the same network will fight. Give that server a fixed IP address first, since it can’t hand an address to itself. And copy any fixed address reservations across from your router before turning its DHCP off.
- In Technitium, open DHCP → Scopes and set up a scope matching your network: the same address range as the router used, your router as the gateway, a lease time (I use 10 days), and both Technitium servers as DNS servers.
- Set a domain name such as
home.arpa(the name reserved for home networks) and turn on DNS updates, so each device is registered asits-name.home.arpa. - Add reservations for anything that needs a fixed address: servers, devices your router forwards ports to, smart plugs and cameras whose apps expect them to stay put.
- Switch over in the same minute: turn DHCP off on the router, then enable the scope in Technitium. Existing devices keep their addresses and move across as their leases renew. Reconnect a phone to test straight away.
Two small forwarding rules Add a Forwarder zone for fritz.box (or your router’s own domain) pointing at the router, so its admin page still opens by name. And on the second server, add Forwarder zones for home.arpa and your reverse zone (for example 0.168.192.in-addr.arpa) pointing at the DHCP server, so device names work whichever server a device asks.
The reward is worth it. Technitium’s dashboard now lists the top clients by name instead of by router, and its DHCP → Leases page doubles as a live list of everything on the network, with one-click reservations.
7. Test it
# a normal domain should resolve on both servers
dig +short @<server-1-ip> example.com
dig +short @<server-2-ip> example.com
# an ad domain should come back empty (blocked)
dig +short @<server-1-ip> doubleclick.net
# device names should work on both servers
dig +short @<server-1-ip> your-phone.home.arpa
dig +short @<server-2-ip> your-phone.home.arpa
Then the real test: stop Technitium on one server and keep browsing. If everything still loads, your redundancy works.
Was it worth switching?
Yes. Ads and known-bad domains are blocked for every device in the house, DNS queries leave the network encrypted, I can see which device is asking for what, and rebooting a server for updates no longer means everyone shouting that the Wi-Fi is broken.
New to Docker? Start with Getting started with Docker and Portainer.

Leave a Reply