Skip to content

Split DNS Strategy

Overview

Split DNS (also called Split-Horizon DNS) allows internal network clients to resolve the same domain names to internal IP addresses while external clients resolve to public IP addresses. This is essential for seamless access to services from both inside and outside your network.

The Problem Split DNS Solves

Without Split DNS

```text Internal user browsing to https://passwords.skaggsfamily.us:

  1. DNS resolves to public IP (your WAN address)
  2. Traffic goes: Device → Router → Internet → Router → Internal Service
  3. Issues: Hairpin NAT problems, slower routing, potential failures

```text

```text

With Split DNS

```text

```text Internal user:

  1. Internal DNS resolves to 172.16.103.x (direct internal IP)
  2. Traffic goes: Device → Internal Service (fast, direct)

External user:

  1. Public DNS resolves to your WAN IP
  2. Traffic goes: Device → Internet → Your Router → Internal Service

```text

```text

Why This Combination

  • Technitium: Excellent split DNS capabilities, authoritative server features
  • AdGuard Home: Family-friendly ad blocking, easy management interface
  • Complementary: Technitium handles split DNS, AdGuard handles filtering/blocking

Architecture

DNS Flow Design

```text

```text Family Devices (VLAN 100) ↓ AdGuard Home (Primary DNS - 172.16.90.10) ├── Ad blocking and filtering ├── Family-safe DNS policies └── Forwards to Technitium for local domains

Technitium DNS (Secondary/Authoritative - 172.16.90.11) ├── Authoritative for *.skaggsfamily.us ├── Split DNS zones └── Forwards other queries to public DNS (1.1.1.1, 8.8.8.8)

External Clients ↓ Public DNS (CloudFlare, etc.) ├── Public DNS records for skaggsfamily.us └── Points to your WAN IP ```text

```text

Split DNS Zone Configuration

Internal Zone (Technitium)

```dns

```dns ; Internal zone for skaggsfamily.us $ORIGIN skaggsfamily.us. $TTL 300

@ IN SOA dns.skaggsfamily.us. admin.skaggsfamily.us. ( 2024092101 ; Serial 3600 ; Refresh 1800 ; Retry 1209600 ; Expire 300 ; Minimum TTL )

; Internal DNS records (resolve to internal IPs) @ IN A 172.16.103.100 ; Main website passwords IN A 172.16.103.101 ; VaultWarden media IN A 172.16.103.102 ; Media server files IN A 172.16.103.103 ; File sharing monitoring IN A 172.16.103.104 ; Grafana auth IN A 172.16.103.105 ; Authentik business IN A 172.16.103.106 ; Business WordPress

; Wildcard for dynamic services

  • IN A 172.16.103.100 ; Default to main ingress

```text

```text

External Zone (Public DNS Provider)

```dns

dns ; Public DNS records (CloudFlare, etc.) skaggsfamily.us. A YOUR_WAN_IP passwords.skaggsfamily.us. A YOUR_WAN_IP media.skaggsfamily.us. A YOUR_WAN_IP files.skaggsfamily.us. A YOUR_WAN_IP monitoring.skaggsfamily.us. A YOUR_WAN_IP auth.skaggsfamily.us. A YOUR_WAN_IP business.skaggsfamily.us. A YOUR_WAN_IPtext

```text

Implementation Strategy

Phase 1: Technitium DNS Server Setup

```yaml

```yaml

Kubernetes deployment for Technitium

apiVersion: apps/v1 kind: Deployment metadata: name: technitium-dns namespace: infrastructure spec: replicas: 1 selector: matchLabels: app: technitium-dns template: metadata: labels: app: technitium-dns spec: containers:

  - name: technitium-dns

    image: technitium/dns-server:latest

    ports:

    - containerPort: 53

      protocol: UDP

    - containerPort: 53

      protocol: TCP

    - containerPort: 5380

      protocol: TCP  # Web interface

    volumeMounts:

    - name: dns-config

      mountPath: /etc/dns/config

    - name: dns-zones

      mountPath: /etc/dns/zones

    env:

    - name: DNS_SERVER_DOMAIN

      value: "dns.skaggsfamily.us"

    - name: DNS_SERVER_ADMIN_PASSWORD

      valueFrom:

        secretKeyRef:
          name: technitium-secret
          key: admin-password
  volumes:

  - name: dns-config

    persistentVolumeClaim:

      claimName: technitium-config-pvc

  - name: dns-zones

    persistentVolumeClaim:

      claimName: technitium-zones-pvc

apiVersion: v1 kind: Service metadata: name: technitium-dns-service namespace: infrastructure spec: selector: app: technitium-dns ports:

  • name: dns-udp

    port: 53

    protocol: UDP targetPort: 53

  • name: dns-tcp

    port: 53

    protocol: TCP targetPort: 53

  • name: web

    port: 5380

    protocol: TCP targetPort: 5380 type: LoadBalancer loadBalancerIP: 172.16.90.11 # Static internal IP ```text

```text

Phase 2: AdGuard Home Integration

```yaml

```yaml

AdGuard Home for ad blocking + DNS filtering

apiVersion: apps/v1 kind: Deployment metadata: name: adguard-home namespace: infrastructure spec: replicas: 1 selector: matchLabels: app: adguard-home template: metadata: labels: app: adguard-home spec: containers:

  - name: adguard-home

    image: adguard/adguardhome:latest

    ports:

    - containerPort: 53

      protocol: UDP

    - containerPort: 53

      protocol: TCP

    - containerPort: 3000

      protocol: TCP  # Web interface

    volumeMounts:

    - name: adguard-work

      mountPath: /opt/adguardhome/work

    - name: adguard-conf

      mountPath: /opt/adguardhome/conf

  volumes:

  - name: adguard-work

    persistentVolumeClaim:

      claimName: adguard-work-pvc

  - name: adguard-conf

    persistentVolumeClaim:

      claimName: adguard-conf-pvc

apiVersion: v1 kind: Service metadata: name: adguard-home-service namespace: infrastructure spec: selector: app: adguard-home ports:

  • name: dns-udp

    port: 53

    protocol: UDP targetPort: 53

  • name: dns-tcp

    port: 53

    protocol: TCP targetPort: 53

  • name: web

    port: 3000

    protocol: TCP targetPort: 3000 type: LoadBalancer loadBalancerIP: 172.16.90.10 # Static internal IP ```text

```text

Configuration Details

Technitium Split DNS Configuration

```json

json { "zones": [ { "name": "skaggsfamily.us", "type": "Primary", "internal": true, "records": [ { "name": "@", "type": "A", "ttl": 300, "rdata": "172.16.103.100" }, { "name": "passwords", "type": "A", "ttl": 300, "rdata": "172.16.103.101" }, { "name": "auth", "type": "A", "ttl": 300, "rdata": "172.16.103.105" } ] } ], "forwarders": [ "1.1.1.1", "8.8.8.8" ], "recursion": true, "allowRecursionOnlyForPrivateNetworks": true }text

```text

AdGuard Home Configuration

```yaml

```yaml

AdGuard Home configuration

dns: bind_hosts:

- 0.0.0.0

port: 53

upstream_dns:

- 172.16.90.11  # Technitium for local domains
- 1.1.1.1
- 8.8.8.8

upstream_dns_file: ""

bootstrap_dns:

- 9.9.9.10
- 149.112.112.10
- 2620:fe::10
- 2620:fe::fe:10

Local DNS rewrites for split DNS

dns_rewrites:

  • domain: "*.skaggsfamily.us"

    answer: "172.16.90.11" # Forward to Technitium

Filtering settings

filters:

  • enabled: true

    url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt

    name: AdGuard DNS filter

  • enabled: true

    url: https://someonewhocares.org/hosts/zero/hosts

    name: Dan Pollock's List

Custom filtering rules for family

user_rules:

  • "||doubleclick.net^"
  • "||googlesyndication.com^"
  • "@@||skaggsfamily.us^$important" # Always allow your domain

```text

```text

Network Configuration

Router DNS Settings (EdgeRouter-X)

```bash

```bash

Configure DHCP to use AdGuard Home as primary DNS

set service dhcp-server shared-network-name LAN subnet 172.16.100.0/24 dns-server 172.16.90.10 set service dhcp-server shared-network-name SERVERS subnet 172.16.90.0/24 dns-server 172.16.90.10 set service dhcp-server shared-network-name LAB subnet 172.16.103.0/24 dns-server 172.16.90.10

Static DNS entries for infrastructure

set system static-host-mapping host-name adguard.skaggsfamily.us inet 172.16.90.10 set system static-host-mapping host-name dns.skaggsfamily.us inet 172.16.90.11

commit save ```text

```text

Kubernetes Ingress Configuration

```yaml

```yaml

Nginx Ingress with proper internal routing

apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: vaultwarden-ingress namespace: vaultwarden annotations: nginx.ingress.kubernetes.io/ssl-redirect: "true" cert-manager.io/cluster-issuer: "letsencrypt-prod" spec: tls:

  • hosts:

    • passwords.skaggsfamily.us

    secretName: vaultwarden-tls

rules:

  • host: passwords.skaggsfamily.us

    http:

    paths:

    • path: /

      pathType: Prefix

      backend: service: name: vaultwarden-service port: number: 80 ```text

```text

Testing and Validation

Internal DNS Testing

```bash

```bash

Test from internal network

nslookup passwords.skaggsfamily.us 172.16.90.10

Should return: 172.16.103.101

dig @172.16.90.10 passwords.skaggsfamily.us

Should return internal IP

Test browsing

curl -k https://passwords.skaggsfamily.us

Should connect directly to internal service

```text

```text

External DNS Testing

```bash

```bash

Test from external network (mobile data, etc.)

nslookup passwords.skaggsfamily.us 8.8.8.8

Should return: YOUR_WAN_IP

Test external access

curl -k https://passwords.skaggsfamily.us

Should route through your public IP

```text

```text

Troubleshooting Commands

```bash

```bash

Check DNS resolution path

dig +trace passwords.skaggsfamily.us

Test specific DNS servers

dig @172.16.90.10 passwords.skaggsfamily.us # AdGuard dig @172.16.90.11 passwords.skaggsfamily.us # Technitium dig @1.1.1.1 passwords.skaggsfamily.us # Public DNS

Check DNS server logs

kubectl logs -n infrastructure deployment/technitium-dns kubectl logs -n infrastructure deployment/adguard-home ```text

```text

Benefits of This Setup

Performance Benefits

  • Faster internal access: Direct routing to services
  • Reduced WAN bandwidth: Internal traffic stays internal
  • Lower latency: No hairpin NAT routing

Security Benefits

  • Ad blocking: AdGuard Home blocks malicious domains
  • Family filtering: Content filtering for children's devices
  • DNS monitoring: Visibility into DNS queries and threats

Operational Benefits

  • Single domain management: Same URLs work everywhere
  • Easy SSL certificates: Let's Encrypt works for internal services
  • Simplified networking: No special internal URLs to remember

Family IT Benefits

  • Transparent operation: Services "just work" from anywhere
  • No user education needed: Same URLs everywhere
  • Mobile device support: Seamless switching between WiFi and cellular

Maintenance and Monitoring

Backup Strategy

```yaml

```yaml

DNS configuration backup

apiVersion: batch/v1 kind: CronJob metadata: name: dns-backup spec: schedule: "0 3 * * *" # Daily at 3 AM jobTemplate: spec: template: spec: containers:

      - name: backup

        image: alpine:latest

        command:

        - /bin/sh
        - -c
        - |

          # Backup Technitium config

          tar -czf /backup/technitium-$(date +%Y%m%d).tar.gz /etc/dns/

          # Backup AdGuard config
          tar -czf /backup/adguard-$(date +%Y%m%d).tar.gz /opt/adguardhome/conf/
        volumeMounts:

        - name: backup-storage

          mountPath: /backup

        - name: dns-config

          mountPath: /etc/dns

          readOnly: true

        - name: adguard-conf

          mountPath: /opt/adguardhome/conf

          readOnly: true

```text

```text

Monitoring Integration

```yaml

```yaml

Prometheus monitoring for DNS services

  • alert: DNSServiceDown

expr: up{job="dns-server"} == 0

for: 1m labels: severity: critical annotations: summary: "DNS service is down"

  • alert: DNSHighQueryLatency

expr: dns_query_duration_seconds > 0.5

for: 5m labels: severity: warning annotations: summary: "DNS queries are slow" ```text

```text


This split DNS strategy provides seamless internal/external access while maintaining security and performance benefits