Technitium DNS: Advanced DNS Server for Homelabs
Feature-rich DNS server with DoH/DoT support. Split-horizon, caching, and blocklists.
Introduction
Domain Name System (DNS) is the phonebook of the internet, translating human-readable domain names into IP addresses. While most people use their ISP’s DNS or public services like Google DNS, running your own DNS server unlocks powerful capabilities: custom domain resolution for homelab services, advanced ad-blocking without client configuration, DNS-over-HTTPS for privacy, split-horizon DNS for internal/external access, and complete control over your network’s naming infrastructure. Technitium DNS Server delivers all these features in a modern, Docker-friendly package with an intuitive web interface.
What You’ll Learn
- Understanding DNS fundamentals and server types
- Installing Technitium DNS via Docker
- Configuring recursive DNS with caching
- Setting up DNS-over-HTTPS (DoH) and DNS-over-TLS (DoT)
- Implementing split-horizon DNS for internal services
- Deploying ad-blocking and malware protection
- Configuring DNSSEC validation for security
- Creating custom DNS records and zones
- Advanced features: conditional forwarding, blocking, logging
- Integration with homelab infrastructure
- Troubleshooting DNS issues
Why Technitium DNS?
Modern Architecture: Built from scratch in C# on .NET, Technitium offers features that legacy DNS servers (BIND, Unbound) require complex configuration to achieve.
Privacy-First: Native support for DNS-over-HTTPS (DoH) and DNS-over-TLS (DoT) encrypts queries, preventing ISP snooping and man-in-the-middle attacks.
Intuitive Web Interface: Configure everything via clean, responsive web UI. No editing arcane configuration files or restarting services constantly.
Docker-Ready: Official Docker image with proper volume management, making deployment and updates trivial.
Ad-Blocking Built-In: Import blocklists directly in the UI, blocking ads and malware network-wide without per-device configuration.
Split-Horizon DNS: Serve different answers based on client source. Internal clients get internal IPs, external clients get public IPs - all from one server.
Active Development: Regular updates with new features, bug fixes, and security patches. Responsive developer community.
Prerequisites
Hardware Requirements:
- CPU: Minimal (1 core sufficient for home use)
- RAM: 512MB minimum, 1GB recommended
- Storage: 2GB for application and logs
- Network: Stable connectivity to DNS root servers
Software Requirements:
- Docker and Docker Compose
- Linux host (Ubuntu, Debian, any Docker-capable OS)
- Static IP for DNS server (highly recommended)
- Basic text editor for configuration files
Knowledge Requirements:
- Basic DNS concepts (A records, CNAME, MX, etc.)
- Understanding of IP addressing and subnets
- Docker basics helpful but not required
- Comfort with web interfaces
Time Commitment:
- Installation: 15-30 minutes
- Basic configuration: 30-60 minutes
- Advanced features: 1-2 hours
- Ongoing maintenance: 15 minutes monthly
DNS Fundamentals
DNS Record Types
Understanding record types is essential for effective DNS management.
A Record (Address)
example.com. IN A 192.0.2.10
Maps hostname to IPv4 address
Most common record type AAAA Record (IPv6 Address)
example.com. IN AAAA 2001:db8::1
Maps hostname to IPv6 address
IPv6 equivalent of A record CNAME Record (Canonical Name)
www.example.com. IN CNAME example.com.
Alias for another hostname
Cannot coexist with other record types at same name MX Record (Mail Exchange)
example.com. IN MX 10 mail.example.com.
Specifies mail servers for domain
Priority number (10) - lower = higher priority TXT Record (Text)
example.com. IN TXT "v=spf1 mx -all"
Arbitrary text data
Used for SPF, DKIM, domain verification NS Record (Name Server)
example.com. IN NS ns1.example.com.
Delegates subdomain to other name servers
Defines authoritative servers for zone PTR Record (Pointer)
10.2.0.192.in-addr.arpa. IN PTR server.example.com.
Reverse DNS lookup (IP to hostname)
Used for email verification, logging SRV Record (Service)
_service._proto.example.com. IN SRV 10 60 5060 server.example.com.
Defines location of services
Used for SIP, XMPP, LDAP, etc.
Format: priority weight port target DNS Server Types
Recursive Resolver (Technitium’s primary role)
Client → Technitium → Root Servers → TLD Servers → Authoritative Server → Response
- Performs full DNS lookup on behalf of clients
- Caches results for performance
- What most "DNS servers" are Authoritative Server
Holds actual DNS records for domains
Responds with definitive answers
Technitium can also act as authoritative for custom zones Forwarder
Passes queries to upstream DNS servers
Simpler than full recursion
Technitium supports both modes DNS Query Flow
1. Client (192.168.1.50) wants www.example.com
2. Client queries local DNS (Technitium at 192.168.1.1)
3. Technitium checks cache
- If cached and not expired: return answer (milliseconds)
- If not cached: proceed to step 4
4. Technitium queries root servers (.)
- Response: "Ask .com servers at 192.0.2.10"
5. Technitium queries .com TLD servers
- Response: "Ask example.com servers at 192.0.2.20"
6. Technitium queries example.com authoritative servers
- Response: "www.example.com is 192.0.2.100"
7. Technitium caches result (TTL: 3600 seconds)
8. Technitium returns answer to client
9. Future queries for www.example.com served from cache Installation via Docker
Docker Compose Deployment (Recommended)
Step 1: Create Project Directory
mkdir -p ~/technitium-dns
cd ~/technitium-dns Step 2: Create docker-compose.yml
version: '3.8'
services:
technitium:
image: technitium/dns-server:latest
container_name: technitium-dns
hostname: dns-server
ports:
# DNS ports
- "53:53/udp" # DNS UDP
- "53:53/tcp" # DNS TCP
# DNS-over-HTTPS and DNS-over-TLS
- "853:853/tcp" # DNS-over-TLS (DoT)
- "443:443/tcp" # DNS-over-HTTPS (DoH) - optional if using reverse proxy
# Web interface
- "5380:5380/tcp" # HTTP web console
# Optional advanced features
# - "67:67/udp" # DHCP (if using Technitium as DHCP server)
# - "80:80/tcp" # HTTP for web certificates
environment:
- DNS_SERVER_DOMAIN=dns-server # Hostname for web interface
- DNS_SERVER_ADMIN_PASSWORD=admin # Change this!
- DNS_SERVER_ADMIN_PASSWORD_FILE=/etc/dns/admin.password # Or use file
- DNS_SERVER_PREFER_IPV6=false # Set true if IPv6 preferred
- DNS_SERVER_OPTIONAL_PROTOCOL_DNS_OVER_HTTP=false # Legacy DoH
- DNS_SERVER_RECURSION=AllowOnlyForPrivateNetworks # Security
- DNS_SERVER_RECURSION_DENIED_NETWORKS= # Optional: block specific networks
- DNS_SERVER_RECURSION_ALLOWED_NETWORKS=127.0.0.1, 192.168.0.0/16, 172.16.0.0/12, 10.0.0.0/8
- DNS_SERVER_ENABLE_BLOCKING=true # Enable ad-blocking
- DNS_SERVER_ALLOW_TXT_BLOCKING_REPORT=true # TXT record for block info
- DNS_SERVER_FORWARDERS= # Leave empty for full recursion, or set upstream
- DNS_SERVER_FORWARDER_PROTOCOL=Tcp # Or Udp, Tls, Https
volumes:
- ./config:/etc/dns # Configuration and zones
- ./logs:/var/log/dns # Logs
restart: unless-stopped
networks:
- dns_network
# Optional: use host network for better performance
# network_mode: host
# Security options
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE # Required to bind to port 53
# Resource limits (adjust as needed)
mem_limit: 512m
cpus: 1.0
networks:
dns_network:
driver: bridge Important Notes:
Change default password immediately!
DNS_SERVER_ADMIN_PASSWORD=YourSecurePasswordHere123! For production, use password file instead:
echo "YourSecurePasswordHere123!" > ~/technitium-dns/config/admin.password
chmod 600 ~/technitium-dns/config/admin.password Then in docker-compose.yml:
- DNS_SERVER_ADMIN_PASSWORD_FILE=/etc/dns/admin.password Step 3: Create Required Directories
mkdir -p ~/technitium-dns/{config,logs}
# Set permissions
chmod 755 ~/technitium-dns/config
chmod 755 ~/technitium-dns/logs Step 4: Deploy
cd ~/technitium-dns
docker-compose up -d
# Monitor startup
docker-compose logs -f technitium
# Wait for: "DNS Server was started successfully" Step 5: Access Web Interface
Navigate to: http://YOUR-SERVER-IP:5380
Default credentials:
Username: admin
Password: (as set in docker-compose.yml) Initial Web Interface Setup
First Login:
1. Login with admin credentials
2. Dashboard appears showing:
- Server status: Running
- Total queries: 0
- Zones: 0
- Cache: Empty
3. Recommended: Change admin password
Settings → Administration → Change Password Step 6: Verify DNS Service
# Test from another machine on network
# Query A record
dig @YOUR-SERVER-IP google.com
# Should return:
# ;; ANSWER SECTION:
# google.com. 300 IN A 142.250.80.46
# Check response time (should be fast after caching)
dig @YOUR-SERVER-IP google.com | grep "Query time" Basic Configuration
Configuring Recursion
Settings → General
DNS Server Settings:
Recursion:
- Enable Recursion: Checked
- Recursion Denied Networks: (leave empty or restrict)
- Recursion Allowed Networks:
127.0.0.1/8
192.168.0.0/16
172.16.0.0/12
10.0.0.0/8
Note: Restricts recursion to private networks (security best practice)
Cache Settings:
- Serve Stale: Enabled (7 days)
Returns cached data even if expired (prevents total failure)
- Max Cache Size: 100 MB
Adjust based on available RAM
Save Configuring Forwarders (Optional)
Option 1: Full Recursion (Recommended)
Settings → Forwarders
Forwarders: (leave empty)
Protocol: None selected
This makes Technitium query root servers directly
Pros: Privacy, no dependency on upstream providers
Cons: Slightly slower initial queries (mitigated by caching) Option 2: Conditional Forwarding
Settings → Forwarders
Add Forwarder:
Protocol: DNS-over-TLS
Forwarder: cloudflare-dns.com (853)
Or: one.one.one.one (853)
Use for: All queries
Or: Specific domains only
Save
Pros: Faster initial queries, encrypted to upstream
Cons: Cloudflare sees your queries (privacy trade-off) Option 3: Split Forwarding
Internal domains: Use local DNS
External domains: Forward to Cloudflare/Google
Zones → Add Zone
Zone: internal.example.com
Type: Primary Zone
Records:
server.internal.example.com → 192.168.1.10
nas.internal.example.com → 192.168.1.20
Forwarders:
(all other queries forwarded to upstream) Blocking Configuration
Settings → Blocking
Enable Blocking: Checked
Block List URLs:
- https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
- https://v.firebog.net/hosts/AdguardDNS.txt
- https://raw.githubusercontent.com/anudeepND/blacklist/master/adservers.txt
- https://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&showintro=0
Quick Filters (optional):
Safe Search: Enable (forces safe search on Google, Bing, etc.)
YouTube Restricted Mode: Enable
Allow List Domains: (whitelist exceptions)
- s.youtube.com (for some embeds)
- clients4.google.com (Android connectivity check)
Update Frequency: Daily at 03:00
Save and Update Now Test Blocking:
# Query blocked domain
dig @YOUR-SERVER-IP doubleclick.net
# Should return:
# ;; ANSWER SECTION:
# doubleclick.net. 60 IN A 0.0.0.0
# Or configured block IP DNS-over-HTTPS (DoH) and DNS-over-TLS (DoT)
Enabling Encrypted DNS
Settings → General → DNS Server Options
Protocols:
DNS-over-TLS:
Enable: Checked
Port: 853
Certificate: (auto-generated self-signed, or custom)
DNS-over-HTTPS:
Enable: Checked
Port: 443 (or 8053 if 443 used by reverse proxy)
Certificate: (use Let's Encrypt via reverse proxy recommended)
DNS-over-HTTP (legacy):
Enable: Unchecked (deprecated)
DNS-over-QUIC:
Enable: Optional (experimental, very fast)
Port: 853
Save Obtaining SSL Certificate
Option 1: Self-Signed (Testing Only)
Technitium auto-generates self-signed certificate
Works for DoT and DoH
Clients will show security warnings
Acceptable for internal testing Option 2: Let’s Encrypt via Reverse Proxy
Nginx reverse proxy for DoH:
# /etc/nginx/sites-available/dns-over-https
server {
listen 443 ssl http2;
server_name dns.example.com;
ssl_certificate /etc/letsencrypt/live/dns.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/dns.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location /dns-query {
proxy_pass https://localhost:8053/dns-query;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
} Obtain certificate:
sudo certbot --nginx -d dns.example.com Option 3: Caddy (Automatic HTTPS)
# Caddyfile
dns.example.com {
reverse_proxy /dns-query localhost:8053
} Caddy automatically obtains and renews Let’s Encrypt certificates.
Client Configuration
Windows (DNS-over-HTTPS):
Settings → Network & Internet → Ethernet/WiFi → Edit
Preferred DNS: YOUR-SERVER-IP
Alternate DNS: (secondary server or empty)
DNS over HTTPS:
On (automatic template)
Or: Manual: https://dns.example.com/dns-query Android (Private DNS / DoT):
Settings → Network → Private DNS
Private DNS provider hostname:
dns.example.com
Or: Automatic (uses system DNS with DoT) iOS (DNS Profile):
Create DNS configuration profile or use app:
- DNSCloak (DoH)
- Cloudflare WARP (configurable with custom endpoint)
Firefox (DNS-over-HTTPS):
Settings → General → Network Settings
Enable DNS over HTTPS
Custom: https://dns.example.com/dns-query macOS/Linux (systemd-resolved):
# /etc/systemd/resolved.conf
[Resolve]
DNS=YOUR-SERVER-IP
DNSOverTLS=yes
Domains=~.
sudo systemctl restart systemd-resolved Split-Horizon DNS Configuration
Serve different answers based on client location.
Use Case Example
External clients (internet):
home.example.com → 203.0.113.50 (public IP) Internal clients (LAN):
home.example.com → 192.168.1.100 (private IP) Creating Custom Zones
Zones → Add Zone
Zone Type: Primary Zone
Zone Name: example.com
Advanced Options:
Internal: Unchecked (authoritative for both)
Add Add Internal Records:
Zones → example.com → Add Record
Type: A
Name: home (creates home.example.com)
TTL: 3600
IPv4 Address: 192.168.1.100
Comments: Internal LAN access
Add Record Create Split View:
Zones → example.com → home → Edit
Enable Geo Blocking or Use Views:
Create Custom View:
View Name: internal
Networks: 192.168.0.0/16, 172.16.0.0/12, 10.0.0.0/8
Answer: 192.168.1.100
Create Custom View:
View Name: external
Networks: 0.0.0.0/0 (all others)
Answer: 203.0.113.50
Save Alternative: Separate Zones
Create internal.example.com zone for LAN-only records:
Zones → Add Zone
Zone: internal.example.com
Type: Primary Zone
Add Records:
nas.internal.example.com → 192.168.1.20
proxmox.internal.example.com → 192.168.1.30
docker.internal.example.com → 192.168.1.40 Configure clients to search internal.example.com domain:
DHCP option: domain-name "internal.example.com"
Or: Manual DNS suffix in client network settings Advanced Features
Conditional Forwarding
Forward specific domains to specific DNS servers.
Settings → Forwarders → Conditional Forwarders
Add Conditional Forwarder:
Domain: ad.company.local
Forwarders:
- 192.168.100.10 (Active Directory DC)
- 192.168.100.11 (Secondary DC)
Protocol: Udp (or Tcp for reliability)
All queries for *.ad.company.local forwarded to AD servers
All other queries use normal recursion Use Cases:
- Corporate VPN: Forward internal domains to company DNS
- Multi-site: Forward remote.example.com to remote site DNS
- Cloud services: Forward aws.internal to AWS Route 53 private zones
DNSSEC Validation
Settings → General → DNSSEC
Enable DNSSEC Validation: Checked
DNSSEC Proof of Non-Existence:
NSEC3 (recommended for privacy)
Trust Anchors:
Auto-configured from root zone
Or: Manual (advanced)
Save Effect:
- Validates DNSSEC-signed domains
- Prevents DNS spoofing and cache poisoning
- Fails queries for improperly signed domains (by design)
Test:
# Query DNSSEC-signed domain
dig @YOUR-SERVER-IP dnssec-failed.org +dnssec
# Should fail or return SERVFAIL (domain intentionally broken)
dig @YOUR-SERVER-IP cloudflare.com +dnssec
# Should succeed with RRSIG records Query Logging and Analysis
Settings → Logs → Query Logs
Enable Query Logging: Checked
Log Options:
- Ignored Queries: Unchecked (log everything)
- Blocked Queries: Checked
- Allowed Queries: Checked (optional - increases log size)
Max Log Days: 7 (or more for analysis)
Export Logs: CSV (for external analysis)
Save View Logs:
Dashboard → Query Logs
Filters:
- Client IP
- Domain queried
- Query type (A, AAAA, MX, etc.)
- Response type (Allowed, Blocked, etc.)
Real-time view with auto-refresh Analyze Top Domains:
Dashboard → Stats
Top Queried Domains:
Shows most frequently requested domains
Identifies talkative devices/services
Top Blocked Domains:
Shows most blocked ad/tracking domains
Verify blocklists working effectively
Top Clients:
Identifies heavy DNS users
Useful for troubleshooting Custom Blocklists
Create Custom Block List:
Settings → Blocking → Block Lists → Custom Lists
Add List:
List Name: Custom Tracking Domains
List Type: Allow or Block
Domains:
telemetry.microsoft.com
metrics.google-analytics.com
tracker.example.com
Or: Upload text file (one domain per line)
Save Regex Blocking:
Settings → Blocking → Advanced
Regex Block List:
^ad[0-9]+..*$
^tracker..*$
.*.doubleclick.net$
Save
Blocks domains matching regex patterns
More flexible than exact match DNS Aliases (CNAME Cloaking Detection)
Prevent CNAME cloaking bypassing blocklists:
Settings → Blocking
Block CNAME Cloaking: Enable
Effect:
If example.com (allowed) has CNAME to tracker.net (blocked)
Technitium blocks entire chain
Prevents tracking via CNAME redirection Monitoring and Maintenance
Dashboard Metrics
Key Metrics to Monitor:
Dashboard → Overview
Total Queries:
- Last hour / day / week
- Trend analysis
Cache Performance:
- Hit rate: Target ~80%+
- Miss rate: Queries requiring recursion
Blocked Queries:
- Percentage blocked
- Effectiveness of blocklists
Top Clients:
- Identify high-usage devices
- Troubleshoot chatty services
Server Health:
- Uptime
- Memory usage
- Query response time Performance Tuning
Settings → General → Cache
Cache Settings:
Serve Stale: 7 days
Serves expired cache if authoritative unreachable
Prevents total DNS failure
Max Cache Entries: 100,000
Adjust based on network size and RAM
Negative Caching: Enable
Caches NXDOMAIN responses
Reduces load on upstream servers
Prefetch: Enable
Proactively refreshes popular cached entries
Improves perceived performance Memory Limits:
mem_limit: 1g # Increase for large networks Backup Configuration
Manual Backup:
Settings → Administration → Backup
Backup Now:
Downloads .zip file with all settings and zones
Store securely offsite Automated Backup:
#!/bin/bash
# backup-technitium.sh
BACKUP_DIR="/backup/technitium"
DATE=$(date +%Y%m%d_%H%M%S)
# Copy config directory
docker cp technitium-dns:/etc/dns $BACKUP_DIR/config_$DATE
# Create archive
cd $BACKUP_DIR
tar -czf technitium_backup_$DATE.tar.gz config_$DATE
# Remove old backups (keep 30 days)
find $BACKUP_DIR -name "technitium_backup_*.tar.gz" -mtime +30 -delete
# Remove temp directory
rm -rf config_$DATE Cron schedule:
crontab -e
# Daily backup at 2 AM
0 2 * * * /path/to/backup-technitium.sh Updating Technitium
Docker Update:
cd ~/technitium-dns
# Pull latest image
docker-compose pull
# Recreate container with new image
docker-compose up -d
# Verify
docker-compose logs -f technitium
# Check version
# Dashboard → About Rollback if needed:
# Stop current version
docker-compose down
# Edit docker-compose.yml
image: technitium/dns-server:10.0.1 # Specific version
# Start
docker-compose up -d Integration with Homelab
DHCP Integration
Set Technitium as DHCP DNS Server:
pfSense/OPNsense:
Services → DHCP Server → LAN
DNS servers:
192.168.1.1 (Technitium IP)
Save and Apply UniFi:
Settings → Networks → Default → DHCP
DHCP Name Server: Manual
DNS Server 1: 192.168.1.1 (Technitium)
DNS Server 2: (secondary or leave empty)
Save Mikrotik:
IP → DHCP Server → Networks
DNS Servers: 192.168.1.1 Router advertisement (IPv6):
Configure router to advertise Technitium as IPv6 DNS via RDNSS Pi-hole Migration
Migrate from Pi-hole to Technitium:
1. Export Pi-hole block lists:
Pi-hole → Group Management → Adlists → Export
2. Import to Technitium:
Settings → Blocking → Block Lists
Add URLs from Pi-hole export
3. Export Pi-hole local DNS records:
Pi-hole → Local DNS → Export
4. Create custom zone in Technitium:
Zones → Add Zone → example.local
Add records manually or via API
5. Update DHCP to point to Technitium
6. Monitor for 24 hours
7. Decommission Pi-hole Home Assistant Integration
Monitor Technitium from Home Assistant:
# configuration.yaml
sensor:
- platform: rest
name: Technitium DNS Queries
resource: http://YOUR-SERVER-IP:5380/api/dashboard/stats/get?token=YOUR_API_TOKEN
value_template: '{{ value_json.response.stats.totalQueries }}'
unit_of_measurement: "queries"
scan_interval: 60
- platform: rest
name: Technitium DNS Blocked
resource: http://YOUR-SERVER-IP:5380/api/dashboard/stats/get?token=YOUR_API_TOKEN
value_template: '{{ value_json.response.stats.totalBlocked }}'
unit_of_measurement: "blocked"
scan_interval: 60 Generate API Token:
Technitium → Settings → Administration → API
Create Token:
Name: HomeAssistant
Permissions: Read-only
Copy token for configuration Prometheus Monitoring
Expose metrics for Prometheus:
# docker-compose.yml addition
labels:
- "prometheus.scrape=true"
- "prometheus.port=5380"
- "prometheus.path=/api/dashboard/stats/get?type=json" Prometheus config:
# prometheus.yml
scrape_configs:
- job_name: 'technitium-dns'
static_configs:
- targets: ['technitium-dns:5380']
metrics_path: '/api/dashboard/stats/get'
params:
type: ['json'] Create Grafana dashboard for visualization.
Troubleshooting
DNS Resolution Failures
Symptom: Domains not resolving
Check 1: Is Technitium running?
docker ps | grep technitium
docker-compose logs technitium
Check 2: Can client reach Technitium?
ping 192.168.1.1 (Technitium IP)
Check 3: Test direct query
dig @192.168.1.1 google.com
nslookup google.com 192.168.1.1
Check 4: Firewall blocking?
sudo ufw status | grep 53
Ensure UDP/TCP 53 allowed
Check 5: Recursion disabled?
Settings → General → Recursion → Enable
Check 6: Network connectivity from Technitium?
docker exec -it technitium-dns ping 8.8.8.8
Docker exec -it technitium-dns dig @8.8.8.8 google.com Slow DNS Performance
Symptom: Queries taking multiple seconds
Check 1: Cache hit rate
Dashboard → Stats
Target: 80%+ cache hits
If low:
- Increase cache size (Settings → General → Cache)
- Enable prefetch
Check 2: Forwarder latency
Settings → Forwarders → Test
Response time should be under 50ms
If high:
- Try different forwarders
- Or: Use full recursion (remove forwarders)
Check 3: Blocked upstream
Dashboard → Query Logs
Look for SERVFAIL responses
May indicate upstream DNS unreachable
Check 4: Resource constraints
docker stats technitium-dns
High memory/CPU?
- Increase resource limits
- Reduce blocklist size
- Disable query logging
Check 5: Network congestion
ping -c 100 192.168.1.1
Packet loss or high latency?
- Check network infrastructure
- Try different DNS server port (non-standard) Blocked Domains Incorrectly
Symptom: Legitimate site blocked
Solution 1: Check which list blocked it
Dashboard → Query Logs
Find domain → Details → Blocked by: [list name]
Solution 2: Whitelist domain
Settings → Blocking → Allow List
Add: example.com
Save
Solution 3: Disable specific blocklist
Settings → Blocking → Block Lists
Uncheck problematic list
Update
Solution 4: Report false positive
Contact blocklist maintainer
GitHub issue or email DoH/DoT Not Working
Symptom: Encrypted DNS fails
Check 1: Certificate valid?
openssl s_client -connect dns.example.com:853
Should show valid certificate chain
Check 2: Port accessible?
telnet dns.example.com 853
nc -zv dns.example.com 853
If fails:
- Check firewall (allow TCP 853)
- Check port forwarding (if remote)
Check 3: Client configuration correct?
Verify hostname matches certificate
Try IP address (will fail with cert warning - expected)
Check 4: Reverse proxy misconfigured?
Test direct connection (bypass proxy)
Check proxy logs for errors Advanced Use Cases
Geo-Blocking
Block queries from specific countries:
Zones → Add Zone
Zone: *.
Type: Wildcard
GeoIP Settings:
Enable GeoIP Blocking: Yes
Blocked Countries: CN, RU, KP
Response: 0.0.0.0
Save
All queries from those countries return null Custom DNS Firewall
Block known malware C2 domains:
Settings → Blocking → Block Lists
Add:
https://malware-filter.gitlab.io/malware-filter/urlhaus-filter-dns.txt
https://osint.digitalside.it/Threat-Intel/lists/latestdomains.txt
Save and Update
Technitium automatically blocks known malware domains DNS Rebinding Protection
Prevent DNS rebinding attacks:
Settings → General → Security
Block Private IP Responses: Enable
Effect:
Technitium refuses to return private IPs for public domains
Prevents rebinding attacks against internal services Dynamic DNS (DDNS) Support
Update DNS records via API:
#!/bin/bash
# ddns-update.sh
API_TOKEN="your_api_token_here"
SERVER="http://192.168.1.1:5380"
ZONE="example.com"
RECORD="home"
CURRENT_IP=$(curl -s ifconfig.me)
# Update A record
curl -X POST "$SERVER/api/zones/records/update?token=$API_TOKEN" -H "Content-Type: application/json" -d "{
"zone": "$ZONE",
"domain": "$RECORD.$ZONE",
"type": "A",
"ttl": 300,
"ipAddress": "$CURRENT_IP"
}"
echo "Updated $RECORD.$ZONE to $CURRENT_IP" Schedule with cron:
*/5 * * * * /path/to/ddns-update.sh Conclusion
Technitium DNS Server brings enterprise-grade DNS capabilities to your homelab with a modern, accessible interface. From basic recursive caching to advanced split-horizon configurations and encrypted DNS protocols, Technitium provides complete control over your network’s naming infrastructure with minimal complexity.
Key Takeaways
- Privacy: DNS-over-HTTPS and DNS-over-TLS encrypt queries
- Control: Full authority over DNS resolution and custom records
- Security: DNSSEC validation, malware blocking, ad filtering
- Flexibility: Split-horizon DNS, conditional forwarding, geo-blocking
- Performance: Intelligent caching and prefetching
Next Steps
Immediate Actions:
- Deploy Technitium via Docker
- Configure recursive DNS
- Enable ad-blocking with popular lists
- Update DHCP to use Technitium
Advanced Projects:
- Set up DNS-over-HTTPS with Let’s Encrypt
- Implement split-horizon DNS for services
- Create custom internal zones
- Integrate monitoring with Grafana
- Build automation for dynamic DNS updates
Additional Resources
- Official Documentation: https://technitium.com/dns/
- GitHub Repository: https://github.com/TechnitiumSoftware/DnsServer
- Community Forum: https://forum.technitium.com/
- API Documentation: https://github.com/TechnitiumSoftware/DnsServer/blob/master/APIDOCS.md
- YouTube: Lawrence Systems - “Why you should run your own DNS server”
Document Version: 1.0 Last Updated: December 16, 2025 Difficulty: Intermediate Estimated Setup Time: 1-2 hours for full deployment Maintenance: Monthly blocklist updates, quarterly configuration review