DNS is the invisible infrastructure that translates human-readable domain names into machine-routable IP addresses. It is so fundamental that when DNS breaks, the internet stops working. Every system administrator must understand DNS, not just conceptually, but operationally: how to configure records, run local resolvers, troubleshoot resolution failures, and secure DNS queries.

This guide covers DNS from the protocol level through to running your own DNS infrastructure for a homelab or production environment.

The DNS Hierarchy

DNS is a distributed, hierarchical database. When you type grafana.example.com into a browser, the resolution follows a chain of authoritative servers:

  1. Root servers (.): 13 logical root server groups (a.root-servers.net through m.root-servers.net) that know where to find TLD servers. There are over 1,500 physical instances distributed globally via anycast.
  2. TLD servers (.com, .org, .net): Know which nameservers are authoritative for each domain under their TLD.
  3. Authoritative nameservers (example.com): Hold the actual DNS records for a domain. These are typically operated by your domain registrar or a DNS provider like Cloudflare.
  4. Recursive resolver: Your ISP or a public resolver (1.1.1.1, 8.8.8.8) that walks the hierarchy on your behalf and caches results.

Caching is critical. Without caching, every DNS query would require multiple round trips to root, TLD, and authoritative servers. TTL (Time To Live) values on DNS records control how long resolvers cache each response. A TTL of 3600 means the record is cached for one hour.

DNS Record Types

Record Type Purpose Example
A Maps hostname to IPv4 address server.example.com. 3600 IN A 93.184.216.34
AAAA Maps hostname to IPv6 address server.example.com. 3600 IN AAAA 2606:2800:220:1::
CNAME Alias one name to another www.example.com. 3600 IN CNAME example.com.
MX Mail server for a domain example.com. 3600 IN MX 10 mail.example.com.
TXT Arbitrary text (SPF, DKIM, verification) example.com. 3600 IN TXT "v=spf1 include:_spf.google.com ~all"
SRV Service location (port + priority) _sip._tcp.example.com. 3600 IN SRV 10 5 5060 sip.example.com.
NS Authoritative nameserver for a zone example.com. 86400 IN NS ns1.example.com.
PTR Reverse DNS (IP to hostname) 34.216.184.93.in-addr.arpa. 3600 IN PTR server.example.com.
SOA Start of Authority (zone metadata) Serial number, refresh intervals, admin contact
CAA Certificate Authority Authorization example.com. 3600 IN CAA 0 issue "letsencrypt.org"
Tip: CNAME records cannot coexist with other record types at the same name. You cannot have a CNAME and an MX record at example.com. This is why the root domain (apex) typically uses A/AAAA records, while subdomains can use CNAMEs. Some DNS providers offer a proprietary "ALIAS" or "ANAME" record to work around this limitation.

Running Your Own DNS Server

Pi-hole: Ad Blocking + Local DNS

Pi-hole is the most popular self-hosted DNS solution. It blocks ads and trackers at the DNS level and provides local DNS resolution for your homelab services:

# Deploy Pi-hole with Docker
docker run -d \
  --name pihole \
  --restart unless-stopped \
  -p 53:53/tcp \
  -p 53:53/udp \
  -p 80:80/tcp \
  -e TZ='America/New_York' \
  -e WEBPASSWORD='your-admin-password' \
  -e PIHOLE_DNS_='1.1.1.1;1.0.0.1' \
  -v pihole_data:/etc/pihole \
  -v pihole_dnsmasq:/etc/dnsmasq.d \
  pihole/pihole:latest

# Add local DNS records via the web UI or CLI:
# Local DNS Records > Add:
# grafana.home.lab    -> 192.168.1.100
# nextcloud.home.lab  -> 192.168.1.100
# usulnet.home.lab    -> 192.168.1.100

# Or add via the command line:
docker exec pihole bash -c \
  'echo "192.168.1.100 grafana.home.lab" >> /etc/pihole/custom.list'
docker exec pihole pihole restartdns

Unbound: Recursive Resolver

Unbound is a recursive DNS resolver that queries root servers directly instead of forwarding to a third-party resolver like Cloudflare or Google. Paired with Pi-hole, it gives you complete DNS privacy:

# /opt/docker/dns/docker-compose.yml
services:
  pihole:
    image: pihole/pihole:latest
    container_name: pihole
    restart: unless-stopped
    ports:
      - "53:53/tcp"
      - "53:53/udp"
      - "8080:80/tcp"
    environment:
      TZ: 'America/New_York'
      WEBPASSWORD: '${PIHOLE_PASSWORD}'
      PIHOLE_DNS_: '172.20.0.2#5335'
    volumes:
      - pihole_data:/etc/pihole
      - pihole_dnsmasq:/etc/dnsmasq.d
    networks:
      dns:
        ipv4_address: 172.20.0.3
    depends_on:
      - unbound

  unbound:
    image: mvance/unbound:latest
    container_name: unbound
    restart: unless-stopped
    volumes:
      - ./unbound.conf:/opt/unbound/etc/unbound/unbound.conf:ro
    networks:
      dns:
        ipv4_address: 172.20.0.2

networks:
  dns:
    ipam:
      config:
        - subnet: 172.20.0.0/24

volumes:
  pihole_data:
  pihole_dnsmasq:
# unbound.conf - Recursive resolver configuration
server:
    verbosity: 0
    interface: 0.0.0.0
    port: 5335
    do-ip4: yes
    do-ip6: no
    do-udp: yes
    do-tcp: yes

    # Security
    hide-identity: yes
    hide-version: yes
    harden-glue: yes
    harden-dnssec-stripped: yes
    use-caps-for-id: no

    # Performance
    num-threads: 2
    msg-cache-size: 64m
    rrset-cache-size: 128m
    cache-min-ttl: 3600
    cache-max-ttl: 86400
    prefetch: yes
    prefetch-key: yes

    # Privacy
    qname-minimisation: yes
    aggressive-nsec: yes

    # Access control
    access-control: 172.20.0.0/24 allow
    access-control: 127.0.0.0/8 allow

    # Root hints (auto-updated)
    root-hints: /opt/unbound/etc/unbound/root.hints

CoreDNS: Flexible DNS Server

CoreDNS is a lightweight, plugin-based DNS server used extensively in Kubernetes. It is excellent for custom DNS configurations:

# Corefile - CoreDNS configuration
home.lab {
    hosts {
        192.168.1.100  server.home.lab
        192.168.1.100  grafana.home.lab
        192.168.1.100  nextcloud.home.lab
        192.168.1.100  usulnet.home.lab
        192.168.1.101  nas.home.lab
        fallthrough
    }
    log
    errors
}

. {
    forward . 1.1.1.1 1.0.0.1
    cache 30
    log
    errors
}

Split-Horizon DNS

Split-horizon (or split-brain) DNS returns different results depending on where the query comes from. Internal clients get private IP addresses, while external clients get public addresses. This is essential for self-hosted services that are accessible both locally and remotely:

# With Pi-hole local DNS:
# Internal clients (192.168.1.0/24) resolve:
# grafana.example.com -> 192.168.1.100 (local IP)

# External clients resolve via public DNS:
# grafana.example.com -> 203.0.113.50 (public IP)

# Pi-hole handles this automatically when you add
# local DNS entries for your domains. Queries from
# your LAN get the local answer; queries from outside
# never hit Pi-hole and use your public DNS records.

# For more complex setups, use conditional forwarding
# or views in BIND:

DNSSEC

DNSSEC (DNS Security Extensions) adds cryptographic signatures to DNS records, preventing forgery and cache poisoning attacks. While you should enable DNSSEC validation in your resolver, implementing DNSSEC signing for your own domains requires careful operational commitment:

# Check if a domain has DNSSEC enabled
dig +dnssec example.com

# Look for the 'ad' flag (Authenticated Data) in the response
# ;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2

# Verify DNSSEC chain
dig +trace +dnssec example.com

# Enable DNSSEC validation in Unbound (enabled by default)
# In unbound.conf:
server:
    # DNSSEC validation
    auto-trust-anchor-file: "/opt/unbound/etc/unbound/root.key"
    val-clean-additional: yes

Troubleshooting DNS with dig and nslookup

dig is the most powerful DNS troubleshooting tool. Learn these commands and you can diagnose almost any DNS issue:

# Basic query
dig example.com

# Query for a specific record type
dig example.com MX
dig example.com TXT
dig example.com AAAA
dig example.com NS

# Query a specific DNS server
dig @1.1.1.1 example.com
dig @192.168.1.100 grafana.home.lab

# Short answer only
dig +short example.com

# Trace the full resolution path
dig +trace example.com

# Check reverse DNS
dig -x 93.184.216.34

# Check all record types
dig example.com ANY

# Query with DNSSEC verification
dig +dnssec +multi example.com

# Check SOA record (zone metadata)
dig example.com SOA

# Measure query time
dig example.com | grep "Query time"

# nslookup alternative (simpler but less detailed)
nslookup example.com
nslookup -type=MX example.com
nslookup example.com 1.1.1.1

Common DNS Problems and Solutions

Problem Symptom Diagnosis Fix
Stale cache Old IP returned after record change dig +trace shows correct, local resolver wrong Wait for TTL to expire or flush cache
Wrong nameserver Queries go to wrong server Check /etc/resolv.conf Fix resolver configuration
NXDOMAIN Domain not found dig +trace to find where chain breaks Check domain registration and NS records
Slow resolution Long page load times dig query time > 100ms Use a faster resolver or run a local cache
SERVFAIL Server failure response Often DNSSEC validation failure Check DNSSEC configuration or disable validation
# Flush DNS cache on different systems:

# systemd-resolved (Ubuntu)
sudo resolvectl flush-caches

# Pi-hole
pihole restartdns reload

# macOS
sudo dscacheutil -flushcache && sudo killall -HUP mDNSResponder

# Windows
ipconfig /flushdns

# Check what DNS server your system is using
resolvectl status
cat /etc/resolv.conf
Warning: Docker containers use their own DNS resolution (127.0.0.11 by default, handled by Docker's embedded DNS server). If your containers cannot resolve external domains, check the Docker daemon's DNS configuration in /etc/docker/daemon.json and the container's --dns flag or Compose dns option.

DNS Best Practices for Self-Hosted Infrastructure

  1. Run a local DNS resolver. Pi-hole + Unbound gives you ad blocking, local DNS, and privacy from third-party resolvers.
  2. Use meaningful hostnames. grafana.home.lab is better than 192.168.1.100:3000.
  3. Set appropriate TTLs. 3600 (1 hour) for most records. Lower (300) for records that change frequently.
  4. Add CAA records. Restrict which Certificate Authorities can issue certificates for your domain.
  5. Monitor DNS resolution. A monitoring tool like Blackbox Exporter can alert you when DNS resolution fails.
  6. Document your DNS setup. Keep a record of all DNS entries, both public and local.

With usulnet managing your Docker infrastructure, services are accessible through a unified dashboard, but DNS remains the foundation that routes traffic to the right place. A well-configured DNS setup with Pi-hole and local records means you can access every service by name, both at home and remotely through your VPN.