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 the router hands out both as DNS 1 and DNS 2.

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.
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. Point your network at them
In your router’s DHCP settings, set the primary DNS to server 1 and the secondary to server 2. Devices pick up the change the next time they renew their lease, or straight away if you reconnect them.
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
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, 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.