DNS Fundamentals - The Internet's Phone Book

Status: Active
Last Updated: 2026-08-14
Category: Networking - Phase 1: Network Fundamentals
Prerequisites: tcp-ip-fundamentals, ip-addressing-subnets
Time: 2-3 hours
Tags: dns, resolution, records, ttl, authoritative, recursive, caching

Summary

How names become addresses: the DNS hierarchy from root servers down to your local resolver, every record type you'll actually encounter (A, AAAA, CNAME, MX, TXT, NS, SRV), the difference between authoritative and recursive servers, how caching and TTLs make lookups fast β€” and make changes slow to propagate. Ends with the hands-on dig skills you'll use for the rest of your career.

🎯 What You'll Learn

By the end of this guide, you'll understand:


Table of Contents

  1. The Problem DNS Solves
  2. The Hierarchy
  3. Anatomy of a Lookup
  4. Record Types You'll Actually Use
  5. Authoritative vs Recursive
  6. TTL, Caching, and "Propagation"
  7. dig: The Only Tool You Need
  8. Choosing Resolvers for Your Machines
  9. Hands-On Exercises
  10. Common Gotchas
  11. Next Steps

πŸ”— Related


The Problem DNS Solves

Computers route by numbers; humans remember names. DNS (Domain Name System) is the globally distributed database that translates between them β€” example.com β†’ 93.184.216.34.

Three properties make DNS one of the internet's great designs:

  1. Distributed β€” no single server knows everything; responsibility is delegated down a tree
  2. Cached everywhere β€” answers are remembered at many layers so the root zone isn't hammered billions of times per second
  3. Delegated β€” you control your domain's records because the .com zone points at your nameservers

You will interact with DNS constantly in self-hosting: pointing domains at your servers, creating internal names for services, running your own resolver (pihole-setup), and debugging why "it works on my laptop but not the server."

The Hierarchy

DNS namespaces are organized as an inverted tree:

                        .  (root)
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
      com          net         org           dev
        β”‚            β”‚           β”‚             β”‚
    example.com   example.net  example.org  fogserv.dev
        β”‚
    www.example.com  Β·  mail.example.com  Β·  api.example.com

Reading a FQDN (fully qualified domain name) right-to-left climbs the tree: www.example.com. = "www, inside example, inside com, inside root." That trailing dot is real β€” it's the root β€” but everyone omits it.

Delegation In Action

Nobody "owns" the whole tree. Instead:

Zone Who runs it What they know
. (root) 13 operator identities (a.root-servers.net … m.root-servers.net), anycasted to thousands of instances worldwide Which servers answer for every TLD
com Verisign + others For each .com domain: which nameservers are authoritative
example.com Whoever registered it (that's you) Every record inside the domain

When you buy mydomain.com, you tell the registrar which nameservers to publish in the .com zone (NS delegation). From then on, the world asks your nameservers β€” or whoever hosts DNS for you (Cloudflare, Route53, your own bind).

# See the delegation chain yourself:
dig com NS                      # who serves .com?
dig example.com NS              # who is authoritative for example.com?

Anatomy of a Lookup

What happens, in full, the first time any machine resolves www.example.com:

Your browser                    Your resolver (e.g. 192.168.1.1 / 1.1.1.1)
     β”‚                                        β”‚
     β”‚ 1. "www.example.com A?"                β”‚
     β”‚ ─────────────────────────────────────► β”‚
     β”‚                                        β”‚ 2. cache miss β†’ ask root
     β”‚                                        β”‚ ── "www.example.com A?" ──► root
     β”‚                                        β”‚ ◄─ "ask .com, here are its servers" ──
     β”‚                                        β”‚ 3. ask .com
     β”‚                                        β”‚ ── "www.example.com A?" ──► a.gtld-servers
     β”‚                                        β”‚ ◄─ "ask ns1.example.com" ──
     β”‚                                        β”‚ 4. ask authoritative
     β”‚                                        β”‚ ── "www.example.com A?" ──► ns1.example.com
     β”‚                                        β”‚ ◄─ "93.184.216.34, TTL 3600" ──
     β”‚ ◄── 93.184.216.34 (cached TTL 3600) ── β”‚

Key observations:

Record Types You'll Actually Use

A zone file is just a set of typed records. These cover ~99% of real work:

Type Purpose Example
A Name β†’ IPv4 www IN A 203.0.113.10
AAAA Name β†’ IPv6 www IN AAAA 2001:db8::10
CNAME Alias β†’ another name blog IN CNAME www (then resolve www again)
MX Mail exchanger for the domain IN MX 10 mail.example.com.
TXT Free-form text (SPF, DKIM, verification) IN TXT "v=spf1 include:_spf.google.com ~all"
NS Nameserver delegation for a zone IN NS ns1.example.com.
SOA Zone metadata (serial, refresh timers) One per zone; mostly machinery
SRV Service location (port included) _sip._tcp IN SRV 10 5 5060 sip.example.com.
PTR Reverse: IP β†’ name 10.113.0.203.in-addr.arpa IN PTR www.example.com.

Rules of Thumb

Wildcards and Subdomain Patterns

*.apps.example.com.  IN A  203.0.113.20     # anything.apps.example.com works

Wildcards pair beautifully with wildcard certificates (later: traefik-ssl-automation) β€” add gitlab.apps, grafana.apps without touching DNS again.

Authoritative vs Recursive

Same protocol, completely different jobs β€” confusing these makes docs unreadable:

Authoritative server Recursive resolver
Answers from Its own zone data Cache + climbing the hierarchy
Knows about Only its zones Everything (by asking around)
Run by Registrars, DNS hosts, you (bind/nsd) ISPs, 1.1.1.1/8.8.8.8, your router, your Pi-hole
Question it gets "What is <www.example.com>?" (definitive) "What is <www.example.com>?" (best effort, cached)
dig @ns1.example.com www.example.com      # ask a specific (authoritative) server
dig @1.1.1.1 www.example.com              # ask a specific (recursive) server

# Flags tell you which kind answered:
;; flags: qr aa rd ra;
#         β”‚  β”‚  β”‚  └─ ra = recursion available β†’ recursive server
#         β”‚  β”‚  └──── rd = recursion desired (your request)
#         β”‚  └─────── aa = authoritative answer ← from the source of truth
#         └────────── qr = query response marker

In your infrastructure both roles appear: Pi-hole acts as your local recursive-ish resolver (actually a filtering forwarder unless you add Unbound), while Traefik's DNS-01 challenges talk to your authoritative provider's API (later lessons).

TTL, Caching, and "Propagation"

Every record ships with a TTL (time to live) in seconds β€” how long any cache may reuse the answer.

www  3600  IN  A  203.0.113.10
     ↑
     TTL = 3600s = 1 hour

The famous "DNS propagation delay" is really cache expiry: nothing pushes your change outward; caches simply serve stale answers until their TTL lapses.

Practical consequences:

# Watch caching happen: run twice, note query time and server path
dig www.wikipedia.org | grep -E "Query time|SERVER|flags"
dig www.wikipedia.org | grep -E "Query time|SERVER|flags"
# Second run: Query time β‰ˆ 1-30ms, flags include ra, no climb needed

# See remaining cache life from your resolver's view:
dig +noall +answer www.wikipedia.org
# www.wikipedia.org.  300  IN  A  185.15.59.224     ← 300 = seconds left in cache

Myth check: "propagation takes 48 hours" comes from ancient default TTLs. With modern TTLs, most changes are fully visible in minutes. If something still resolves wrong after an hour, suspect you edited the wrong zone/wrong nameservers β€” not propagation.

dig: The Only Tool You Need

dig (Domain Information Groper) is verbose, scriptable, and universal. Core patterns:

dig example.com                     # basic A lookup via your default resolver
dig example.com AAAA                # specific record type
dig example.com ANY +noall +answer  # all types (many servers restrict this now)

dig @8.8.8.8 example.com            # bypass your resolver β€” test another one
dig +short example.com              # just the answer line(s)
dig +trace example.com              # replay the full root→TLD→auth walk
dig -x 203.0.113.10                 # reverse lookup (PTR)

dig example.com MX                  # where does its mail go?
dig example.com TXT                 # SPF/DKIM/domain verification
dig _dmarc.example.com TXT          # DMARC policy

Reading a full answer:

; <<>> DiG 9.18 <<>> www.example.com
;; QUESTION SECTION:
;www.example.com.  IN A

;; ANSWER SECTION:
www.example.com. 86400 IN A 93.184.216.34
β”‚                       β”‚       β”‚       β”‚
name                    TTL     class   the answer

;; AUTHORITY SECTION:      ← nameservers that vouch for this zone
;; ADDITIONAL SECTION:     ← bonus records (e.g., IPs of those NS)
;; Query time: 42 msec     ← 0-5ms after caching kicks in
;; SERVER: 192.168.1.1#53  ← who actually answered you

nslookup and host do less but exist everywhere; know them for quick checks:

host example.com
nslookup example.com 1.1.1.1

Choosing Resolvers for Your Machines

Every device needs at least one recursive resolver. Options, best-practice order for self-hosters:

Choice Pros Cons
Your own Pi-hole/Unbound (LAN IP) Filtering, local records, full visibility Single point of failure β€” always configure two
Public: 1.1.1.1, 9.9.9.9, 8.8.8.8 Fast, reliable, easy Third party sees all your lookups
ISP resolver (DHCP default) Zero config Often slow, ad-injecting, unreliable
# Inspect what a box is actually using (three different mechanisms!):
resolvectl status                  # systemd-resolved (modern desktops/servers)
cat /etc/resolv.conf               # classic mechanism (often symlinked to systemd)
nmcli dev show | grep DNS          # NetworkManager's view

# Test whether DoT/DoH (encrypted DNS) is in play:
resolvectl status | grep -i "DNSOverTLS\|Current DNS Server"

Two failure modes worth memorizing early:

  1. Resolver dead β†’ everything times out, ping-by-IP works fine. Fix: second DNS entry.
  2. Wrong search domain β†’ ssh web01 tries web01.lan.example.com. first, fails slow. Check search/ndots settings when shortnames misbehave β€” a notorious Kubernetes/K3s latency trap (containers with ndots:5 generate 5Γ— the lookups; relevant later in kb/containers/k0s-networking).

Local overrides for lab machines:

# Quick and dirty (until reboot/regenerate):
echo "203.0.113.10 gitlab.internal.test" | sudo tee -a /etc/hosts

# Proper: local records on your own DNS server β†’ lesson [local-dns-records](local-dns-records)

Hands-On Exercises

# 1. Trace a domain end-to-end and watch each hop:
dig +trace fogserv.dev | tail -20

# 2. Compare answers across public resolvers (do they agree? same TTLs?)
for r in 1.1.1.1 8.8.8.8 9.9.9.9; do echo "== $r"; dig +short @$r example.com; done

# 3. Dissect a real domain's email setup:
dig gmail.com MX +short
dig gmail.com TXT +short | head -3
dig _dmarc.gmail.com TXT +short

# 4. Measure caching benefit:
time dig +stats slashdot.org | grep "Query time"   # first call
time dig +stats slashdot.org | grep "Query time"   # second call β€” see the drop?

# 5. Find your machine's resolver chain and draw it on paper:
resolvectl status || cat /etc/resolv.conf

# 6. Break DNS deliberately (in a VM!) and observe the symptom shape:
sudo resolvectl dns eth0 192.0.2.53   # black-hole resolver
curl -m5 https://example.com          # note: TIMEOUT, not refused
ping -c2 93.184.216.34                # ...but IP connectivity is fine
sudo resolvectl flush-cache           # restore: resolvectl revert eth0

That last exercise encodes the most valuable diagnostic reflex in this whole course: if names fail but IPs work, it's DNS. Say it out loud; you'll use it monthly.

Common Gotchas

Symptom Cause Fix
Works by IP, times out by name Resolver unreachable/misconfigured dig @1.1.1.1 vs plain dig; fix /etc/resolv.conf
Change made, old answer persists Cache honoring TTL Wait out TTL; lower TTL next time pre-change
New subdomain instantly NXDOMAIN Negative caching of the miss Flush local (resolvectl flush-cache), wait negative-TTL elsewhere
dig fine, browser fails Browser uses DoH to a different resolver Check browser secure-DNS setting
Intermittent slow SSH/curl by hostname Search-domain + ndots retries Qualify names with trailing dot: ssh web01.
Mail not arriving anywhere Missing/broken SPF, MX trailing-dot typo dig yourdomain MX, dig yourdomain TXT β€” read them like a stranger would
Domain resolves externally, not internally Split-horizon zone missing the record Add it locally too β€” see local-dns-records
Random NXDOMAIN bursts Resolver failover to broken upstream Configure two resolvers; monitor with dig +stats cron job

Next Steps

Names now resolve; make them resolve reliably and debuggably:

  1. network-troubleshooting β€” the full toolkit when any layer misbehaves
  2. pihole-setup β€” run DNS yourself, filter ads network-wide
  3. local-dns-records β€” split-horizon DNS for your internal services

πŸ”— Related

Choose Theme

Your selection is saved locally.

Neural Cacophony
Aperture v2
Flux v1
Mosaic Chaos
Nexus v1
Nexus Zest
Prism v2
Synapse