DNS: Finding The Server
This site already has a DNS Deep Dive in the Web Infrastructure track — that’s the owner’s view: registrars, zone files, managing records, why “propagation” feels slow. This module is the other half: the runtime view — what actually happens in the milliseconds when your code resolves a name, and exactly what to do when it fails. Same system, opposite end of the wire.
The Goal
By the end of this module you can:
- Trace a name lookup through every cache and server it touches — and say which step answered, and why the second lookup is near-instant
- Read
nslookupoutput line by line instead of pattern-matching for an IP - Distinguish NXDOMAIN, SERVFAIL, and a DNS timeout — three different errors pointing at three different culprits
- Explain the JVM’s own DNS cache and the classic failover incident it causes
- Override DNS locally with the hosts file — and run your own API on your own fake domain in two minutes
- Recognize
UnknownHostExceptionand curl error 6 as DNS problems before wasting time elsewhere
The Lesson
The resolution chain — who answers what
Your code calls restTemplate.getForObject("https://api.example.com/..."). Java doesn’t speak DNS itself — it asks the OS, and the OS works through a chain, stopping at the first level that knows the answer:
flowchart LR
A[Your code] --> B[OS stub resolver]
B --> C[Hosts file then OS cache]
C --> D[Recursive resolver]
D --> E[Root server]
D --> F[TLD server]
D --> G[Authoritative server]
| Step | Who | What it knows |
|---|---|---|
| Stub resolver | Your OS (Windows DNS Client service) | Nothing — it just asks the configured resolver and caches replies |
| Hosts file + OS cache | Your machine | Manual overrides, plus answers from the last few minutes |
| Recursive resolver | Your ISP, or 8.8.8.8 / 1.1.1.1 | Does the actual legwork; caches aggressively for everyone using it |
| Root server | 13 logical servers worldwide | One thing only: where the TLD servers are (“for .com, ask over there”) |
| TLD server | Runs .com, .in, etc. | Where each domain’s authoritative servers are |
| Authoritative server | Whoever hosts the domain’s zone | The actual records — the source of truth |
The cold-path lookup (resolver → root → TLD → authoritative) happens rarely. Almost every real lookup is answered from a cache in step 2 or 3 — which is the next point.
TTL — one number, two faces
Every DNS answer carries a TTL: how many seconds anyone may cache it. The Deep Dive covered why this makes record changes slow from the owner’s side. From the runtime side, flip it around: TTL is why DNS is fast. Without caching, every HTTP request on earth would queue up at thirteen root servers. With it, your hundredth call to the same API resolves in microseconds from local memory.
Same number, two faces: high TTL = fast lookups, slow changes. Low TTL = agile changes, more lookup traffic. As the engineer consuming DNS, the face you care about is: when something changes upstream, every cache between you and the authoritative server keeps lying to you until its TTL runs out — including one cache most developers don’t know exists, inside your own JVM.
Record types you’ll actually query
The full record table (MX, NS, SOA, the A-vs-CNAME apex rule) lives in the Deep Dive — don’t relearn it here. From code, you touch a shorter list:
| Record | You hit it when | Note |
|---|---|---|
| A / AAAA | Every connection to a hostname | A = IPv4, AAAA = IPv6. The answer your socket actually needs |
| CNAME | Resolving anything hosted on a platform | An alias hop: www.github.com → github.com → A record. Resolvers chase the chain for you |
| SRV | One-liner: maps a service name to a host and port — how Kubernetes and Consul do service discovery | |
| TXT | One-liner: free-form text — domain verification, SPF; you’ll query it while debugging email or ownership checks |
What the JVM adds — the hidden cache
Here’s the layer that bites Java developers specifically: InetAddress keeps its own cache of successful lookups, on top of the OS cache. Default: 30 seconds for successful lookups, 10 seconds for failures — but older JVMs and some setups cached successful lookups forever (until restart).
The classic incident, and it’s a fintech favourite: the database fails over. Ops updates the DNS record to point at the new primary within seconds. Every other system recovers. Your Java service keeps connecting to the old IP — the dead one — because the JVM cached the lookup and never asks again. Someone restarts the app “to see if it helps,” it helps, and nobody learns anything. Now you know: the fix is the JVM setting, not the restart.
The knob lives in java.security (under your JDK’s conf/security folder):
networkaddress.cache.ttl=30
networkaddress.cache.negative.ttl=10
Interview-grade sentence: “DNS answers are cached at the browser, OS, resolver — and inside the JVM, and the JVM one is the one that survives a record change and causes failover incidents.”
Failure modes — three errors, three culprits
DNS fails in exactly three flavours, and they are not the same error:
| Error | Who said it | What it means | Your move |
|---|---|---|---|
| NXDOMAIN | The authoritative server, definitively | ”This name does not exist.” Typo, wrong domain, record genuinely missing | Check the spelling, check the environment (is it api.staging. vs api.?) |
| SERVFAIL | The recursive resolver | ”I tried and failed to get an answer.” Broken zone config, DNSSEC validation failure, unreachable authoritative servers | Not your typo — the domain’s DNS is sick. Try another resolver to confirm |
| Timeout | Nobody | The resolver itself never answered. Network down, firewall blocking port 53, wrong resolver configured | This isn’t about the domain at all — your path to DNS is broken |
Note the rhyme with TCP’s failure table from module 02: a definitive negative answer (NXDOMAIN, like refused) means the system works and is telling you a truth; silence (timeout) means infrastructure is broken.
In Java, all three usually surface as one exception: java.net.UnknownHostException. In curl, error 6: Could not resolve host. When you see either, stop reading application logs — the request never left the building. Drop to nslookup to find which of the three flavours you’re in.
The hosts file — the override that beats everything
Before any cache, before any resolver, Windows checks one plain text file:
C:\Windows\System32\drivers\etc\hosts
A line like 127.0.0.1 splitease.local means: for this machine, that name resolves to that IP, full stop. No TTL, no resolver, no network. It beats every cache because it’s checked first.
Three things to know about it:
- Legitimate use: test a real domain against a new server before changing public DNS, or give local services memorable names (you’ll do this in Build This).
- Why malware loves it: one line redirecting a bank’s domain to an attacker’s IP hijacks every program on the machine — which is why editing it needs admin rights and why antivirus watches it.
- The debugging trap: a forgotten hosts entry from last year’s testing produces “DNS lies” that no amount of
ipconfig /flushdnsclears. When one machine resolves a name differently from every other machine, check the hosts file first.
Split-horizon, in two lines
Some networks answer the same DNS question differently depending on who’s asking: on the office VPN, internal-api.company.com resolves to a private 10.x address; off VPN, it doesn’t resolve at all. So “works on VPN, UnknownHostException off it” isn’t flaky DNS — it’s DNS doing its job for two different audiences.
See It Move
Step through a full cold-cache resolution below (press ▶ Play):
1. You type ayrisglobal.com. Your browser asks its Resolver: “what’s the IP?” Nothing cached — so the hunt begins.
2. The resolver asks a Root server. Root doesn’t know the IP, but points the way: “ask the .com servers.”
3. The resolver asks the .com TLD server. It says: “ask ayrisglobal’s own nameserver.”
4. The resolver asks the Authoritative nameserver — the one that actually knows: “ayrisglobal.com = 104.21.45.12.”
5. The resolver hands the IP back to your browser and caches it (so next time is instant). Only now can the browser open a connection.
Step through it and notice:
- The root and TLD servers never hand out the final IP — each just points one level down the hierarchy. Only the authoritative server knows the answer.
- The recursive resolver does all the legwork and then caches the result — for everyone who shares that resolver, not just you.
- The answer travels back with a TTL attached — that number decides everything about the warm-cache run.
- Warm cache: two steps and done. This is the path nearly every request in production actually takes.
Check The Concept
How This Shows Up At Work
- The failover that didn’t. Postmortem line you’ll actually read someday: “DNS was updated in 40 seconds; the Java services required restarts.” Now you’re the person in the room who says
networkaddress.cache.ttland turns a recurring incident into a one-line config fix. - “Works on VPN, broken at home.” A teammate’s
UnknownHostExceptionfor an internal API, reproducible only off-VPN. That’s split-horizon DNS, not a flaky app — five minutes instead of a half-day of confusion. - The lying laptop. One developer’s machine resolves the staging domain to an IP nobody recognizes. Every cache was flushed, the mystery persists — until someone checks the hosts file and finds a test entry from months ago. You’ll check it first.
- Interview round. “What happens when you type a URL?” — the DNS chapter of that answer is this module: hosts file, OS cache, recursive resolver, root, TLD, authoritative, TTL on the way back, plus the JVM cache if they probe Java specifics. Most candidates say “DNS resolves the domain” in four words and stall.
- Reading the right error. A payment webhook integration fails. Junior reads HTTP logs; you read
Could not resolve host, realize no packet ever left, runnslookupagainst two resolvers, and report “their domain’s DNS is returning SERVFAIL — it’s on their side” with evidence.
Build This
You’ll dissect real lookups, then give your own API its own domain without spending a rupee on a registrar. Reminder from module 02: type curl.exe, not curl, in PowerShell.
- Resolve a name and read every line:
nslookup example.com
Server: dns.google
Address: 8.8.8.8
Non-authoritative answer:
Name: example.com
Addresses: 2606:2800:21f:cb07:6820:80da:af6b:8b2c
93.184.215.14
Line by line: Server / Address is which resolver answered you (yours will show your ISP’s or router’s — mine here is Google’s). Non-authoritative answer means it came from the resolver’s cache, not straight from the authoritative server — you’re seeing the warm path from the visualizer. Then the name and its addresses: one AAAA (IPv6) and one A (IPv4). Your numbers may differ — that’s DNS being a distributed system, not a bug.
- Ask a specific resolver instead of your default:
nslookup example.com 8.8.8.8
Same shape of answer, but now you chose the resolver. This is your standard move when you suspect your default resolver is lying or sick: ask 8.8.8.8 and 1.1.1.1, compare. Two resolvers agreeing means the answer is real; disagreeing means caches mid-change or split-horizon.
- Follow a CNAME chain:
nslookup -type=CNAME www.github.com
Non-authoritative answer:
www.github.com canonical name = github.com
www.github.com has no address of its own — it’s an alias pointing at github.com, which holds the A record. Resolvers chase this automatically; you just made the hop visible.
- Run your API on your own domain. Open Notepad as Administrator (it’s a protected file), then open:
notepad C:\Windows\System32\drivers\etc\hosts
Add this line at the bottom and save:
127.0.0.1 splitease.local
With splitease-api running, call it by name:
curl.exe http://splitease.local:8080/actuator/health
{"status":"UP"}
Sit with that for a second: no registrar, no nameserver, no money — and splitease.local resolves, because the hosts file outranks the entire global DNS hierarchy on your machine. (Try nslookup splitease.local too — it fails, because nslookup queries DNS servers directly and skips the hosts file. Ping and curl use the real OS chain; nslookup doesn’t. Remember that asymmetry.)
-
Now remove the line, save, and run the curl again. Stale hosts entries are exactly the debugging trap from the lesson — don’t leave one behind. If the name still resolves,
ipconfig /flushdnsclears the OS cache. -
Break it — NXDOMAIN. Ask for a name that cannot exist:
nslookup this-domain-does-not-exist-9381.com
*** dns.google can't find this-domain-does-not-exist-9381.com: Non-existent domain
“Non-existent domain” is Windows-speak for NXDOMAIN: a definitive, authoritative “no such name.” Notice it came back fast — the system worked perfectly and delivered a negative answer.
- Break it — the error your code will show. Now curl the same name:
curl.exe http://this-domain-does-not-exist-9381.com
curl: (6) Could not resolve host: this-domain-does-not-exist-9381.com
Error 6 is curl’s surface for any resolution failure — it flattens NXDOMAIN, SERVFAIL, and timeout into one message, exactly like Java flattens them into UnknownHostException. That’s why step 6 matters: the application error tells you it’s DNS; only nslookup tells you which DNS problem.
Where to Practice
| Resource | What to do there | How long |
|---|---|---|
| howdns.works | Read the full comic start to finish — the resolution chain as a story; then redraw the chain from memory | 20 min |
| Cloudflare Learning Center (cloudflare.com/learning) | Read “What is DNS?” and “What is a DNS server?” — map each server type to a step you saw in the visualizer | 25 min |
| curl docs (curl.se) | Look up the --resolve flag — curl’s built-in way to fake DNS for one command, no hosts file needed; try it against your API | 10 min |
Check Yourself
- Walk the cold-cache chain: who does your stub resolver ask, and who does the legwork from there?
- What does “Non-authoritative answer” in nslookup output actually tell you?
- NXDOMAIN vs SERVFAIL vs timeout — one sentence each on who is speaking and what’s broken.
- Your Java app keeps hitting a dead IP after a DNS failover. Name the cache responsible and the setting that controls it.
- Why does
nslookup splitease.localfail whilecurl splitease.local:8080succeeds, given your hosts file entry? - Why does the same TTL value make DNS fast and make changes slow?
- A name resolves to a private 10.x address on the office VPN and fails to resolve at home. What is this called and is it a bug?
UnknownHostExceptionappears in your logs. What’s the first command you run, and what are you trying to distinguish?
Answers
-
The stub resolver checks the hosts file and OS cache, then asks the configured recursive resolver (ISP, 8.8.8.8, 1.1.1.1). On a cache miss, the recursive resolver does the legwork: root server → TLD server → authoritative server, then caches the answer and returns it.
-
The answer came from the resolver’s cache, not directly from the domain’s authoritative server. It’s almost always fine — it just means you’re on the warm path, and the answer could be up to one TTL stale.
-
NXDOMAIN: the authoritative side definitively says the name doesn’t exist — check spelling/environment. SERVFAIL: the recursive resolver tried and failed to get an answer — the domain’s DNS setup is sick. Timeout: the resolver itself never replied — your path to DNS (network, port 53) is broken.
-
The JVM’s own
InetAddresscache. Controlled bynetworkaddress.cache.ttl(andnetworkaddress.cache.negative.ttlfor failures) in the JDK’sjava.securityfile — 30 seconds by default today, effectively forever in older setups. -
nslookupsends queries directly to a DNS server, bypassing the OS resolution chain entirely — so it never sees the hosts file.curlresolves through the OS, where the hosts file is checked before everything else. -
Caches may serve the answer for TTL seconds without re-asking — that’s what makes lookups near-instant. The same permission means a changed record keeps being served stale from every cache until each one’s TTL expires.
-
Split-horizon DNS. Not a bug — the network deliberately gives different answers to internal and external askers, so internal names simply don’t exist outside.
-
nslookup <the-hostname>(then again against 8.8.8.8). You’re distinguishing NXDOMAIN (name is wrong/missing) from SERVFAIL (their DNS is broken) from timeout (your resolver path is broken) — three different culprits hiding behind one Java exception.
Explain it out loud: Narrate what happens in the first 50 milliseconds after your code calls an API by hostname for the very first time — every cache checked, every server asked, what travels back, and where the answer gets stored. Then narrate the second call. If the two stories sound the same, revisit TTL and the warm path.
Still Unclear?
I ran nslookup against my default resolver and against 8.8.8.8 and got
different IPs for the same domain. List the legitimate reasons this happens
(CDNs, caches mid-TTL, split-horizon) and how I'd tell which one it is.
Then quiz me with three made-up nslookup outputs and ask me to diagnose each.
Explain the JVM DNS cache incident properly: a Spring Boot app, HikariCP,
a PostgreSQL failover where DNS now points at a new IP. Walk through what
each layer (OS cache, JVM InetAddress cache, the pool's existing TCP
connections) does, and why BOTH the DNS cache and the pool matter here.
Here is the hosts file syntax I used and the curl output I got: [paste].
It is not resolving. Walk me through the checklist: admin rights, file
encoding, ipconfig /flushdns, and how to prove which resolution step is
failing — without editing anything for me.
Why AI Can’t Do This For You
DNS failures are environmental: the answer depends on which machine asked, through which resolver, with which caches warm, behind which VPN. AI sees none of that. It can describe NXDOMAIN beautifully, but it can’t run nslookup from your box and your production pod and compare — and that comparison is the diagnosis. The engineer who personally watched the same name resolve differently through two resolvers debugs in minutes what others escalate in hours.
There’s also a trust layer: hosts-file hijacks, split-horizon surprises, and stale JVM caches all produce systems that lie convincingly. The only defense is knowing the resolution order cold — what’s checked first, what can override what — so when a machine swears a domain points somewhere impossible, you know exactly which of the five layers is lying. You built that order by hand today, including faking your own domain and watching nslookup ignore it.
Module done? Add it to today’s tracker