Networking intermediate ⏱ 45 minutes

Technitium DNS: Advanced DNS Server for Homelabs

Feature-rich DNS server with DoH/DoT support. Split-horizon, caching, and blocklists.

By homelabbing.io • 2025-12-17
#technitium#dns#doh#dot#privacy

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

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


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