Day 2 · Networking II — DNS & Mail¶
Yesterday you moved packets between numbers. But nobody types
142.250.x.xinto a browser — we use names. Today you learn the system that turnsexample.cominto an address, how to interrogate it withdig, and how the same system quietly runs the world's email — which you'll prove by standing up a real mail server and routing it with your own DNS records.
Learning Objectives¶
- Explain how a name resolves to an IP, step by step, from your machine outward
- Query DNS directly with
digand read the answer - Recognize the common record types and what each is for
- Find where your machine's own resolver is configured (
/etc/hosts,resolv.conf,systemd-resolved) - Understand the DNS records that make email work — MX, SPF, DKIM, DMARC
- Stand up a real mail server (GreenMail in Docker) and send & receive email routed by your own DNS records
Theory · ~20 min¶
1. DNS is a hierarchy¶
DNS (Domain Name System) maps human names to machine addresses. No single server holds every name — the namespace is a tree, and different servers own different branches:
. (root)
┌──────────┼───────────┐
.com .org .np ← top-level domains (TLDs)
│ │
example fundevops ← domains (you register one)
│ │
┌─────┴─────┐ ┌─────┴─────┐
www mail www mail ← hosts/records in your zone
You read a name right to left, most-general first. Every name technically ends in a dot — the invisible root:
www.example.com.
│ │ │ └─ "." the root
│ │ └──── ".com" — the top-level domain (TLD)
│ └──────────── "example.com" — the domain you register & control
└──────────────── "www" — a host/record inside your zone
Whoever controls a level runs its nameservers and delegates the level below: root points to .com's servers, .com points to example.com's servers, and you run the records inside example.com.
📺 Watch — How a DNS Server Works (PowerCert, ~6 min)
A 6-minute animated tour of the resolver → root → TLD → authoritative chain you just read about.
2. How a name resolves — the flow¶
Your machine doesn't walk the tree itself. It hands the question to a recursive resolver (your ISP's, or a public one like 8.8.8.8), which does the legwork and caches the answer:
Your app: "IP for www.example.com?"
│
├─ 1. OS checks /etc/hosts (local override) and its cache
├─ 2. OS asks its recursive resolver (from /etc/resolv.conf)
│
│ the resolver walks the hierarchy (iterative queries):
│ 3. ROOT server → "ask the .com servers"
│ 4. .COM server → "ask example.com's nameservers"
│ 5. example.com's NS → "www is 93.184.x.x" ← authoritative answer
│
└─ 6. resolver caches it (for the TTL) and hands the IP back
Two terms worth knowing: your query to the resolver is recursive ("get me the final answer"); the resolver's queries out to root/TLD/authoritative servers are iterative ("just tell me who to ask next"). The cache in step 6 means the next lookup skips straight to the answer until the TTL expires.
TTL — why DNS changes aren't instant
Every record carries a TTL (time-to-live) in seconds — how long resolvers may cache it. Change a record with a 3600s TTL and some of the world keeps seeing the old value for up to an hour. Before a migration, you lower the TTL in advance so the switch propagates fast.
3. The record types you'll meet¶
A DNS zone is a set of records. The ones that matter day-to-day:
| Record | Maps | Example / use |
|---|---|---|
| A | name → IPv4 | example.com → 93.184.215.14 |
| AAAA | name → IPv6 | example.com → 2606:2800:... |
| CNAME | name → another name | www → example.com (an alias) |
| MX | domain → mail server(s) | where email for @example.com goes |
| TXT | name → free text | SPF, DKIM, domain verification |
| NS | domain → its nameservers | who is authoritative for the zone |
| SOA | — | the zone's "start of authority" metadata |
| PTR | IP → name | reverse DNS (used by mail servers) |
A record vs CNAME
An A record points at an address; a CNAME points at another name (which is then resolved). Use A for the apex (example.com), CNAME for subdomains that should follow another host (www, app → a load balancer's name).
4. Where your machine's DNS is configured¶
Three places decide how names resolve on your box:
/etc/hosts— a static override, checked first. Great for local testing (pointmyapp.localat127.0.0.1)./etc/resolv.conf— lists the resolver(s) to ask. On Ubuntu 24.04 this is managed by systemd-resolved and usually points at the stub127.0.0.53.resolvectl— the tool to inspect what systemd-resolved is actually doing.
5. DNS also runs your email¶
Here's the part people miss: email delivery is a DNS problem. When a server wants to email you@example.com, it asks DNS for the domain's MX record to find the mail server. Three more DNS records decide whether that mail is trusted — this is how the internet fights spoofing:
| Record | Type | Answers the question |
|---|---|---|
| MX | MX | Which server receives mail for this domain? |
| SPF | TXT | Which servers are allowed to send as this domain? |
| DKIM | TXT | Is this message cryptographically signed by the domain? |
| DMARC | TXT | What should happen to mail that fails SPF/DKIM? |
Get these wrong and your mail silently lands in spam — or vanishes.
A test server is easy; production mail is brutal
In the lab you'll run GreenMail, a self-contained test mail server — perfect for seeing SMTP, IMAP, and DNS click together. But a production inbox is famously one of the hardest jobs in ops: your server's IP needs a clean reputation, correct PTR (reverse DNS), and perfect SPF/DKIM/DMARC, or the big providers silently drop your mail. That's why real projects reach for a managed sender (SES, Postmark) instead of self-hosting a mailbox.
Lab · ~50 min¶
Work inside your Vagrant VM. Install the DNS tools:
Step 1 — Your first dig¶
dig example.com # the full response — read the ANSWER SECTION
dig +short example.com # just the address
dig example.com A # only A (IPv4) records
dig example.com AAAA # only AAAA (IPv6)
In the full output, note the ANSWER SECTION and the number beside the record — that's the TTL counting down.
Step 2 — Explore the record types¶
dig +short google.com MX # mail servers (note the priority numbers)
dig +short google.com NS # authoritative nameservers
dig +short google.com TXT # text records — you'll see an SPF entry here
dig example.com SOA # zone authority metadata
Step 3 — Watch the hierarchy resolve¶
+trace walks the tree yourself — root → TLD → authoritative — instead of asking your cached resolver:
Read it top to bottom: the root servers refer you to .org, which refers you to Wikipedia's nameservers, which give the final answer. That's steps 3–5 from the theory, made visible.
Step 4 — Query a specific resolver, and reverse lookups¶
dig @1.1.1.1 example.com # ask Cloudflare's resolver directly
dig @8.8.8.8 example.com # ask Google's — compare the TTLs (caching!)
dig -x 8.8.8.8 # reverse: IP → name (PTR record)
Step 5 — Where your own machine resolves names¶
cat /etc/hosts # static overrides, checked first
cat /etc/resolv.conf # note it points at 127.0.0.53 — the systemd-resolved stub
resolvectl status # the real upstream resolvers your VM uses
Now prove /etc/hosts wins. Add a local override and test it:
echo "127.0.0.1 myapp.local" | sudo tee -a /etc/hosts
dig +short myapp.local # dig ignores /etc/hosts...
getent hosts myapp.local # ...but the OS resolver (what apps use) honors it → 127.0.0.1
ping -c1 myapp.local # resolves to localhost
dig vs the OS resolver
dig talks straight to DNS servers, so it skips /etc/hosts. Real programs use the OS resolver (getent, ping, curl), which checks /etc/hosts first. That difference trips up everyone once — now it won't trip you.
Remove the test override when you're done:
Step 6 — Inspect the mail records of a real domain¶
dig +short google.com MX # who receives Google's mail
dig +short google.com TXT | grep spf # SPF: allowed senders
dig +short _dmarc.google.com TXT # DMARC policy
The DMARC record lives at the special _dmarc.<domain> name — read its p= value (none, quarantine, or reject): that's the domain's instruction for mail that fails authentication.
Step 7 — Real-world demo: run a mail server, route it with DNS¶
This is where the theory pays off — run an actual mail server, point DNS at it (A + MX), then send a real email between two accounts and watch it travel the full path: DNS → MX → SMTP → IMAP.
This demo runs on a public cloud VM, not Vagrant
Mail needs a public IP, a real domain, and open ports — a local VM can't offer those. Use a cloud VM with a public IP (you'll provision EC2 properly in Week 4; here we just hand you the commands), and don't worry that Docker is only taught on Days 5–7 — copy-paste for now. Examples use fundevops.com; substitute your own domain.
7.1 — Open the ports. In the VM's firewall / security group, allow inbound TCP 22 (SSH), 3025 (SMTP), 3143 (IMAP), 8080 (GreenMail API).
7.2 — Install Docker (Ubuntu 24.04):
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER # then log out and back in for this to take effect
docker --version
7.3 — Run GreenMail — a self-contained test server that speaks both SMTP and IMAP:
docker run -d --restart unless-stopped --name greenmail \
-e GREENMAIL_OPTS="-Dgreenmail.setup.test.all -Dgreenmail.hostname=0.0.0.0 -Dgreenmail.auth.disabled -Dgreenmail.verbose" \
-p 3025:3025 -p 3143:3143 -p 8080:8080 \
greenmail/standalone:latest
docker logs greenmail | head # confirm it started (note '-Dgreenmail.auth.disabled')
3025 = SMTP (send), 3143 = IMAP (receive). auth.disabled auto-creates any mailbox on first login and sets login = the full email address — ideal for a demo, never for production.
7.4 — Point DNS at it (the payoff). In your domain's DNS, add two records — sections 1–3 made real:
| Type | Name | Value |
|---|---|---|
| A | mail.fundevops.com |
<EC2_PUBLIC_IP> |
| MX | fundevops.com |
10 mail.fundevops.com |
Verify they resolve, from anywhere:
dig +short mail.fundevops.com A # → your server's IP (the A record)
dig +short fundevops.com MX # → 10 mail.fundevops.com (the MX record)
The A record turns the name into your server's IP; the MX record is what a real sending server would look up to find where @fundevops.com mail goes. (GreenMail is self-contained, so Thunderbird connects to it directly by that hostname — which also proves your A record works.)
7.5 — Add two accounts in Thunderbird (File → New → Existing Mail Account → Configure manually). With auth.disabled, the username is the full email; the password can be anything.
| Setting | Sender | Receiver |
|---|---|---|
| Email / IMAP username | sender@fundevops.com |
receiver@fundevops.com |
| Incoming (IMAP) | mail.fundevops.com : 3143 |
same : 3143 |
| Outgoing (SMTP) | mail.fundevops.com : 3025 |
same : 3025 |
| Connection security | None | None |
| IMAP auth | Normal password | Normal password |
| SMTP auth | No authentication (blank user) | No authentication (blank user) |
7.6 — Send and receive. Tail the server, then from sender@ send a message to receiver@fundevops.com — the To: must match the receiver's IMAP username character-for-character:
With verbose on you'll watch the SMTP RCPT TO:<receiver@fundevops.com> land; hit Get Messages on the receiver and the email appears. You just ran the whole path — DNS (A/MX) → SMTP (3025) → IMAP (3143) — the real-world scenario, end to end.
More detail — SMTP vs IMAP vs POP3 (and their secure ports)
Mail uses two kinds of protocol: one to send (SMTP) and one to read (IMAP or POP3). Each has a plaintext port and a TLS-encrypted one:
| Protocol | Job | Plain / STARTTLS | Implicit TLS |
|---|---|---|---|
| SMTP (relay) | send server → server | 25 | — |
| SMTP (submission) | send from a mail app → server | 587 | 465 |
| IMAP | read mail kept on the server (multi-device sync) | 143 | 993 |
| POP3 | download mail to one device, then delete from server | 110 | 995 |
- IMAP vs POP3: IMAP leaves mail on the server and syncs folders across every device (the modern default); POP3 pulls it to one machine and usually removes it from the server.
- STARTTLS vs implicit TLS: STARTTLS opens plaintext on the normal port (25/587/143) then upgrades to encryption; implicit TLS (465/993/995) is encrypted from the first byte on a dedicated port. In production you expose only the encrypted variants.
- Why GreenMail used
3025/3143: it simply adds3000to the standard ports (25→3025,143→3143), so the container needs no root and sidesteps the commonly-blocked port 25.
Tear it down — it's an open relay
auth.disabled + public ports means anyone can use your server. This is a demo only — remove it and re-close the ports when done:
Advanced Topics¶
- Run your own resolver —
dnsmasqorunboundfor local caching and split-horizon DNS → Ubuntu — dnsmasq - DNS over HTTPS/TLS — encrypting the lookups themselves so the network can't snoop or tamper → Cloudflare — DNS over HTTPS
- The full email trust stack — how SPF, DKIM, and DMARC actually validate a message → Cloudflare — email security
- Registering & delegating a domain — glue records, NS delegation, and how your
.com.npbecomes authoritative → How DNS delegation works - DNSSEC — cryptographically signing a zone so resolvers can detect forged or cache-poisoned answers → Cloudflare — how DNSSEC works
- The apex-
CNAMEproblem — why you can'tCNAMEa root domain (example.com), and how providers fake it with ALIAS/ANAME/CNAME-flattening (you'll meet this pointing your domain at Go-Live) → Cloudflare — CNAME flattening - DNS-based load balancing — round-robin, weighted, and geo-routed records that spread traffic before it ever reaches Nginx (Day 4) → Cloudflare — DNS load balancing
Assignment¶
-
Map your own domain. Use the domain you registered in Week 1 (Day 1). It already has authoritative nameservers, even before you add any records:
Then design the two records you'll create at Go-Live — a web A record and a mail MX — as adig NS <yourdomain> # your domain's nameservers (they exist today) dig <yourdomain> +trace | tail -15 # watch the hierarchy delegate down to themType | Name | Value | TTLtable, and add one line on why you'd set a low TTL right before launch. -
Run your own mail server — locally, in a few commands. No domain or cloud needed: everything stays inside your Vagrant VM, using
test.localas the domain.Find yourcurl -fsSL https://get.docker.com | sudo sh # Docker (previewed now; covered fully on Day 5) sudo docker run -d --name gm -p 3025:3025 -p 3143:3143 \ -e GREENMAIL_OPTS="-Dgreenmail.setup.test.all -Dgreenmail.auth.disabled -Dgreenmail.verbose" \ greenmail/standalone:latest sudo apt install -y swaks swaks --server localhost:3025 --from alice@test.local --to bob@test.local \ --header "Subject: Hello" --body "My first self-hosted email"RCPT TO:<bob@test.local>line insudo docker logs gm, then send a second message to a different address (carol@test.local) and confirm GreenMail auto-created that mailbox too. Clean up withsudo docker rm -f gm. Deliverable: your twoswakscommands and the two matching log lines.
Further Reading¶
Watch
- How a DNS Server Works (PowerCert, ~6 min) — the resolver → root → TLD → authoritative chain, animated
- Everything You Need to Know About DNS — Crash Course: System Design #4 — a fast, visual tour of how DNS resolves
- How DNS works — a friendly comic — the resolution walk, illustrated
Reference
- Cloudflare Learning — What is DNS? — records, resolvers, and caching explained
- Cloudflare — SPF, DKIM & DMARC — the email-trust records from Section 5
Tools
