A lightweight, privacy-first DNS server for Raspberry Pi 3B β ad blocking, authoritative zones, RFC 2136 DDNS, Split-Horizon, Let's Encrypt DNS-01, and Master/Slave clustering in a single Go binary using ~25 MB RAM.
Website & Docs: domudns.net
Every existing DNS solution was either too heavy for a Raspberry Pi 3B or lacked features needed for a proper homelab:
| Feature | DomU DNS | Pi-hole | AdGuard Home | Technitium |
|---|---|---|---|---|
| RAM on Pi 3B | ~25 MB | 80β160 MB | 50β130 MB | 200 MB+ |
| Ad blocking (220k+ domains) | β | β | β | β |
| Authoritative DNS zones | β | β | β | β |
| RFC 2136 DDNS (TSIG) | β | β | β | β |
| Split-Horizon DNS | β | β | β | Partial |
| Zone Transfer AXFR/IXFR | β | β | β | β |
| Master/Slave Cluster | β | β | β | β |
| DoH + DoT | β | Partial | β | β |
| Let's Encrypt DNS-01 | β | β | β | Partial |
| DNSSEC signing | β | β | β | β |
| LRU Cache + Warming | β | β | β | β |
| No database required | β | β | β | β |
| Runtime | Go binary | C/FTL+dnsmasq | Go | .NET runtime |
- π‘οΈ Ad & Tracker Blocking β 220k+ domains, O(1) lookup, wildcard/regex support
- π Authoritative DNS Zones β A, AAAA, MX, CNAME, PTR, TXT, SRV, CAA, NS; JSON file backend
- π RFC 2136 DDNS β ISC dhcpd / Kea DHCP integration via TSIG; auto PTR records
- π Split-Horizon DNS β different answers for internal vs. external clients
- π Master/Slave Cluster β HMAC-SHA256, atomic JSON writes, no database
- π DoH + DoT β RFC 8484 / RFC 7858 with your own certificates
- π Let's Encrypt DNS-01 β built-in ACME provider; Traefik httpreq, Certbot plugin, acme.sh/Proxmox
- π« DNS Rebinding Protection β blocks external domains resolving to private IPs
- β‘ LRU Cache + Warming β preloads top-N query-log domains at startup
- π Next.js Dashboard β real-time stats, zone management, query log, cluster status
- π Zone Transfer AXFR/IXFR β ACL-protected, TCP-only
- π‘ DHCP Lease Sync β dnsmasq / FritzBox lease files β DNS records
- π Named API Keys β per-service API keys with revocation
- π Prometheus Metrics β queries, latency, cache hit rate
- βοΈ Live Config Reload β upstream DNS, block mode, log level without restart
| Metric | Value |
|---|---|
| RAM on Raspberry Pi 3B | ~25 MB |
| Blocklist size | 220,000+ domains |
| Blocklist lookup | O(1) β hash map |
| Response time (cache hit) | 0 ms |
| Response time (authoritative) | 0 ms |
| Response time (upstream) | 1β8 ms |
| Cache capacity | 10,000 entries (~5 MB) |
# 1. Clone & build for Raspberry Pi 3B (ARMv7)
git clone https://github.com/mw7101/domudns.git
cd domudns && make build-arm
# 2. Copy to Pi
scp build/domudns-arm pi@<PI_IP>:/usr/local/bin/domudns
# 3. Install service & start
scp scripts/domudns.service pi@<PI_IP>:/tmp/
ssh pi@<PI_IP> "sudo mv /tmp/domudns.service /etc/systemd/system/ && sudo systemctl enable --now domudns"
# 4. Open setup wizard
open http://<PI_IP>/setupNo database required. All data is stored as JSON files under
/var/lib/domudns/data/. Full docs: domudns.net/docs/quickstart.html
- Go 1.24+ (build machine only)
- Raspberry Pi 3B with Debian Bookworm/Raspbian β or any Linux AMD64/ARM64/ARMv7
- SSH access to the target host
- Port 53 free (stop
systemd-resolvedif needed)
git clone https://github.com/mw7101/domudns.git
cd domudns
# Raspberry Pi 3B (ARMv7)
make build-arm
# ARM64 (Pi 4/5, modern SBCs)
make build-arm64
# AMD64 (x86 server, NAS, VPS)
make build# Create directories
ssh pi@<PI_IP> "sudo mkdir -p /etc/domudns /var/lib/domudns/data /usr/local/bin"
# Copy binary
scp build/domudns-arm pi@<PI_IP>:/tmp/domudns
ssh pi@<PI_IP> "sudo mv /tmp/domudns /usr/local/bin/domudns && sudo chmod +x /usr/local/bin/domudns"Create /etc/domudns/config.yaml on the host:
cluster:
role: "master"
data_dir: "/var/lib/domudns/data"
dnsserver:
listen: "[::]:53"
upstream:
- "9.9.9.9" # Quad9
- "149.112.112.112"
http:
listen: ":80"
system:
log_level: "info"scp scripts/domudns.service pi@<PI_IP>:/tmp/
ssh pi@<PI_IP> "
sudo mv /tmp/domudns.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now domudns
"Open http://<PI_IP>/setup in your browser. The wizard guides you through:
- Set admin username and password
- Generate a secure API key
- Configure blocklist sources
- Add your local DNS zones
Full reference: domudns.net/docs/config.html
cluster:
role: "master" # "master" | "slave"
data_dir: "/var/lib/domudns/data"
# slaves:
# - "http://<SLAVE_IP>:80"
dnsserver:
listen: "[::]:53"
upstream:
- "9.9.9.9"
- "149.112.112.112"
cache:
enabled: true
max_entries: 10000
warmup_count: 200 # Preload top domains from query log at startup
# DNS over HTTPS (RFC 8484)
doh:
enabled: false
path: "/dns-query"
# DNS over TLS (RFC 7858)
dot:
enabled: false
listen: "[::]:853"
blocklist:
enabled: true
file_path: "/var/lib/domudns/blocklist.hosts"
fetch_interval: 24h
block_mode: "zero_ip" # "zero_ip" | "nxdomain"
default_urls:
- "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts"
http:
listen: "0.0.0.0:80"
system:
log_level: info
metrics:
enabled: true
listen: "0.0.0.0:9090"# Cluster sync secret (HMAC-SHA256, same on all nodes β required for clustering)
export DOMUDNS_SYNC_SECRET="<64-hex-characters>"Pi 1 (Master) ββββ HTTP Push βββ Pi 2 (Slave)
- Master pushes zone/blocklist/config changes to all slaves via HMAC-SHA256-signed HTTP
- Slaves poll master as fallback every 30s
- Set both IPs as DNS servers in your router's DHCP settings β automatic failover
# Master (/etc/domudns/config.yaml)
cluster:
role: "master"
slaves:
- "http://<SLAVE_IP>:80"
# Slave (/etc/domudns/config.yaml)
cluster:
role: "slave"
master_url: "http://<MASTER_IP>:80"Full guide: domudns.net/docs/cluster.html
DomU DNS acts as a built-in ACME DNS-01 provider β no open port 80 required. Works with:
- Traefik β native httpreq provider support
- Certbot β DNS plugin (
certbot-dns-domudns) - acme.sh / Proxmox β shell hook script included
Full guide: domudns.net/docs/letsencrypt.html
Incoming query (port 53)
β
1. Blocklist check (O(1) hash lookup)
β
2. DDNS UPDATE handler (RFC 2136, TSIG)
β
3. AXFR/IXFR handler (zone transfers)
β
4. ACME challenge TXT (_acme-challenge.*)
β
5. Authoritative zone (Split-Horizon view-aware)
β
6. LRU Cache
β
7. Conditional forwarding (domain-specific upstreams)
β
8. Default upstream (round-robin UDP, TCP fallback)
β
9. DNS Rebinding Protection
β
Return response
Full reference: domudns.net/docs/api.html
# Create a zone
curl -X POST http://<PI_IP>/api/zones \
-H "Authorization: Bearer <API_KEY>" \
-H "Content-Type: application/json" \
-d '{"domain": "home.lan", "ttl": 3600}'
# Add a record
curl -X POST http://<PI_IP>/api/zones/home.lan/records \
-H "Authorization: Bearer <API_KEY>" \
-H "Content-Type: application/json" \
-d '{"name": "nas", "type": "A", "ttl": 3600, "value": "192.168.1.100"}'
# Live config update (no restart)
curl -X PATCH http://<PI_IP>/api/config \
-H "Authorization: Bearer <API_KEY>" \
-H "Content-Type: application/json" \
-d '{"dnsserver": {"upstream": ["9.9.9.9", "149.112.112.112"]}}'| Mode | Description | Config |
|---|---|---|
| Standalone | Single node, full functionality | role: "master", no slaves: |
| Master | Pushes changes to slaves | role: "master" + slaves: [...] |
| Slave | Receives from master, read-only API | role: "slave" + master_url: ... |
make test # All tests (unit + integration + security)
make test-unit
make test-integration
make lint && make fmt
sudo make run # Port 53 requires root# Service status
sudo systemctl status domudns
sudo journalctl -u domudns -f
# Test DNS
dig @<PI_IP> google.com
dig @<PI_IP> ads.doubleclick.net # Should return 0.0.0.0 (blocked)
# Health check
curl http://<PI_IP>/api/health
# Reset password (forgot credentials)
echo '{}' > /var/lib/domudns/data/auth.json
sudo systemctl restart domudns
# β login with admin/admin β setup wizardMIT License β see LICENSE