Fri, July 4, 2025 · 8 min read

DNS Demystified 5: TTL, Propagation, /etc/hosts, and Linux Resolution Order

“It will take 24-48 hours to propagate.” Every sysadmin has heard this. Most of the time, it is wrong. This article explains what actually happens when you change a DNS record, and shows you tools to control resolution order on Linux.

TTL — Time to Live

TTL is a number in seconds that tells recursive resolvers how long to cache a DNS record. It is set by the zone owner on each individual record (or globally via $TTL in the zone file).

rootlog.in.  300  IN  A  185.199.108.153
             ^^^
             TTL = 300 seconds (5 minutes)

The TTL is specified in the RDATA section of every RR — it is not a separate field. The resolver reads the TTL from the response and sets its cache timer accordingly. The countdown starts from the moment the response is received, not from the moment the zone file was last updated.

Reference: RFC 1035 section 4.1.3 — TTL field in resource record format. Reference: RFC 2308 — Negative Caching of DNS Queries (NCACHE), defines how NXDOMAIN responses are cached.

Why TTL Matters

  • Short TTL (30-300s): Changes propagate quickly. Use during migrations, failover scenarios, or before planned changes.
  • Long TTL (86400s = 24h): Reduces query load on authoritative servers. Use for stable, rarely-changed records like NS and SOA.

Real-World TTL Recommendations

Record TypeTypical TTLRationale
A/AAAA (production)300-3600Balance between stability and change speed
A/AAAA (CDN)60-300Fast failover for CDN endpoints
MX3600-86400Email servers rarely change IP
TXT (SPF/DKIM)3600-86400Infrequent changes
NS86400-172800Rarely changes, high cost of stale data
SOA minimum300-3600Controls negative caching (NCACHE, RFC 2308)

Pro tip: Do not use 86400 (24h) TTL for A records unless the IP is truly static. If your server IP changes and the TTL is 24 hours, you will have downtime for a full day. Start with 300 and increase to 3600 once you are confident the record is stable. CDNs often use 60 seconds or less.

Propagation — The Myth

There is no such thing as “DNS propagation” in the way most people describe it. There is no central authority that pushes changes to all resolvers. Here is what actually happens when you update a DNS record:

  1. You change the A record on the authoritative nameserver.
  2. The change is live immediately on that authoritative server. Any query that reaches the authoritative server directly (bypassing caches) gets the new record.
  3. Recursive resolvers that have the old record cached will keep serving it until the TTL expires. They do not check for updates proactively.
  4. Resolvers that have never queried this domain, or whose cache has expired, will fetch the new record immediately.

So the delay is not propagation — it is cache expiry on each individual resolver. There is no push mechanism. There is no central update. Every resolver independently waits out its local copy of the TTL.

Controlling the Transition

Before changing a production record:

  1. Reduce the TTL to 300 (5 minutes) at least 24 hours in advance.
  2. Wait for the old TTL to expire across all resolvers. Check with dig +ttlid from multiple locations.
  3. Make the record change.
  4. Verify by querying authoritative servers directly.
  5. Confirm from multiple recursive resolvers that the new value is served.
  6. Restore TTL to its original value after confirming stability.
# Step 1: Check current TTL
dig rootlog.in +ttlid
 
# Step 2: After TTL reduction, verify authoritative
dig @dns1.p01.nsone.net rootlog.in +short
 
# Step 3: Check that recursive resolvers have picked up the change
dig @1.1.1.1 rootlog.in +short
dig @8.8.8.8 rootlog.in +short

Real-world context: The “24-48 hour propagation” myth persists because registrars and DNS providers set default TTLs of 86400 (24h) or even 172800 (48h). When a user changes a record without first reducing the TTL, they end up waiting 24-48 hours for caches to expire. This is not propagation — it is the predictable consequence of a high TTL. Set TTLs low before making changes.

/etc/hosts — The Local Override File

The /etc/hosts file (man 5 hosts) maps hostnames to IP addresses directly, bypassing DNS entirely.

127.0.0.1   localhost
127.0.1.1   myhost
185.199.108.153  rootlog.in
192.168.1.50  nas.lan
0.0.0.0    doubleclick.net

Reference: man 5 hosts — the hosts file format. Reference: RFC 952 — hostname specifications (the original DARPA standard).

Practical Uses

Blocking domains — Redirect unwanted domains to localhost or 0.0.0.0:

0.0.0.0    ads.example.com
0.0.0.0    tracking.example.com

Using 0.0.0.0 causes the connection to fail immediately (no route to host) rather than waiting for a timeout. Many system-wide ad-blockers like Pi-hole operate at the DNS level, but a simple /etc/hosts file works for individual machines.

Local development — Override production domains to point to local servers:

127.0.0.1   myapp.local
127.0.0.1   api.myapp.local

Network isolation — Bypass DNS for critical infrastructure during failures:

10.0.0.1    ns1.internal.company.com
10.0.0.2    ns2.internal.company.com

Bypassing DNS blocking — Some ISPs block domains at the DNS level. If you know the IP, add it to /etc/hosts.

Pro tip: The 0.0.0.0 address for blocking is deliberate — it is not a valid destination IP, so connections fail instantly with “Network is unreachable.” Do not use 127.0.0.1 for blocking, as it creates a connection to a local service that may exist, and the application will wait for a timeout.

Format

IP_ADDRESS  CANONICAL_NAME  [ALIASES...]

Multiple hostnames can map to the same IP:

127.0.0.1   localhost.localdomain  localhost  dev.local

Name Resolution Order in Linux

Linux does not query DNS directly. It uses a configurable resolution order defined in /etc/nsswitch.conf (man 5 nsswitch.conf). The relevant line:

hosts: files dns mdns4

This tells the system to check name resolution sources in order:

  1. files — Check /etc/hosts first
  2. dns — Query DNS via /etc/resolv.conf
  3. mdns4 — Multicast DNS (Avahi/Zeroconf)

Reference: man 5 nsswitch.conf — the full Name Service Switch configuration reference. Reference: man 3 gethostbyname — the glibc function that implements this lookup.

How Resolution Works at the Glibc Level

When an application calls gethostbyname("rootlog.in"):

  1. glibc reads /etc/nsswitch.conf
  2. Finds hosts: files dns
  3. Calls the nss_files module — checks /etc/hosts line by line
  4. If no match is found, calls the nss_dns module — queries DNS
  5. Returns the result to the application
# Trace the actual resolution path used by the system
strace -e trace=open,read getent hosts rootlog.in 2>&1 | grep "hosts\|resolv"

You will see glibc opening /etc/hosts first, then /etc/resolv.conf.

Customising the Order

Prioritise DNS over /etc/hosts (not recommended — reduces security):

hosts: dns files

Add mdns after DNS:

hosts: files dns mdns4_minimal [NOTFOUND=return] mdns4

Disable DNS entirely (air-gapped systems):

hosts: files

Checking the Current Order

getent hosts rootlog.in

getent follows the same nsswitch resolution order as every other application. Use it to verify what the system will actually resolve:

getent hosts rootlog.in      # follows nsswitch order — checks /etc/hosts first
dig +short rootlog.in        # always queries DNS directly (bypasses nsswitch)

Pro tip: If getent hosts and dig return different results, /etc/hosts has an entry that is overriding DNS. This is a common source of “why does this server see a different IP for my domain?”

Common Resolution Order Pitfalls

VPN Clients and /etc/hosts

VPN clients often add internal domains to /etc/hosts. If a VPN entry points to a stale IP, it silently overrides DNS for that domain — no TTL to expire, no cache to clear. The only fix is manually editing /etc/hosts.

Container Environments

Docker containers inherit the host’s /etc/resolv.conf but have their own /etc/hosts. Docker’s embedded DNS server (127.0.0.11) handles container name resolution. If you override a container hostname in /etc/hosts, Docker’s DNS is bypassed entirely.

Real-world context: This is a common issue in Kubernetes pods. The pod’s /etc/hosts is managed by the kubelet, and the pod’s /etc/resolv.conf points to the cluster DNS service (e.g., kube-dns). If an init script modifies /etc/hosts inside the pod, it can silently break resolution for any hostname that matches the entry.

The systemd-resolved Detour

On systemd systems, /etc/resolv.conf often points to 127.0.0.53 — the systemd-resolved stub. This adds an additional caching layer between the application and the actual DNS servers. systemd-resolved maintains its own cache, which may serve stale data even after the upstream TTL has expired.

Flush it:

resolvectl flush-caches

nscd (Name Service Cache Daemon)

Older Linux systems run nscd, which caches hosts entries including /etc/hosts lookups. If you edit /etc/hosts, nscd may serve the old entry:

nscd -i hosts    # invalidate the hosts cache

Testing Resolution Order

Create a test entry in /etc/hosts:

127.0.0.2   test-resolution-order.com

Then compare:

getent hosts test-resolution-order.com   # returns 127.0.0.2 from /etc/hosts
dig +short test-resolution-order.com     # returns real IP from DNS (or NXDOMAIN)

The difference confirms that /etc/hosts is checked first by getent but dig bypasses it entirely.

Key Takeaways

  • TTL controls cache duration on recursive resolvers — the countdown starts when the response is received.
  • “Propagation delay” is actually cache expiry. Reduce TTL before making changes, then wait for the old TTL to expire.
  • /etc/hosts (man 5 hosts) overrides DNS and is checked first by the standard resolution order (hosts: files dns).
  • /etc/nsswitch.conf (man 5 nsswitch.conf) controls the resolution order — change it to customise the lookup sequence.
  • Use getent hosts to see what the system will actually resolve (it follows nsswitch).
  • Use dig to bypass nsswitch and query DNS directly.
  • systemd-resolved (resolvectl flush-caches) and nscd (nscd -i hosts) add caching layers that may serve stale data.
  • The “24-48 hour propagation” is a myth. The truth is “24-48 hours of TTL that you forgot to lower.”

Further Reading

  • man 5 hosts — the hosts file format and location.
  • man 5 nsswitch.conf — Name Service Switch configuration.
  • man 5 resolv.conf — resolver configuration format.
  • man 8 systemd-resolved — systemd DNS stub resolver.
  • man 8 nscd — Name Service Cache Daemon.
  • RFC 2308 — Negative Caching of DNS Queries (NCACHE).
  • https://linux.die.net/man/3/gethostbyname

In the final article of this series, we will build our own DNS server with BIND for the rootlog.in domain.