Module 02 — Resource Records, Zone Files & Reading a Domain
Updated 20 August 2026
Module 01 gave you the shape of a record and exactly one type. This module gives you the rest of the types, the file they are written in, and — in Part D — the working recipes for the job you are actually handed: "here is a domain, tell me what exists under it and where it points".
🧠 concept → 🧪 exercise → ✅ expected result (hidden) → 🎯 interview questions (answers hidden)
Prerequisite: Module 01. You already know how to read a dig response, what the aa flag means, how TTLs count down, and how to tell NXDOMAIN from NODATA — all four are used constantly here.
Part A · The anatomy of a record
A1 · The five fields, and why DNS thinks in sets
DNS works the same way. That is why all the records of one type share a TTL, and why an automation script that "adds one record" through an API can silently throw the other five away.
Every record, of every type, has the same five fields. You met them in Module 01:
seamless.se. 60 IN A 52.77.52.233
│ │ │ │ └── RDATA - the value, format depends on TYPE
│ │ │ └────────── TYPE - what kind of record
│ │ └──────────────── CLASS - always IN in practice
│ └─────────────────────── TTL - seconds this may be cached
└──────────────────────────────────── NAME - the owner nameWhat Module 01 did not tell you is that DNS almost never handles one record at a time.
Three consequences follow directly from that, and they explain behaviour that otherwise looks arbitrary:
- Every record in an RRset must have the same TTL. They expire together because they are cached as one object. If you configure different TTLs, the server picks one and silently discards the others
- You cannot add or delete "one record". You replace the whole set. This is why provider consoles make you edit a multi-line text box rather than a single value, and why a careless API call that sends one A record wipes out the other five
- There is no order. The set is unordered; any rotation you see is the server or resolver shuffling on the way out
🧪 Exercise A1.1 — Find an RRset with more than one member and check the TTLs
dig +noall +answer seamless.se MX✅ Expected result — click to reveal
seamless.se. 3600 IN MX 10 aspmx.l.google.com.
seamless.se. 3600 IN MX 20 alt1.aspmx.l.google.com.
seamless.se. 3600 IN MX 20 alt2.aspmx.l.google.com.
seamless.se. 3600 IN MX 30 aspmx2.googlemail.com.
seamless.se. 3600 IN MX 30 aspmx3.googlemail.com.The order you see will differ — the set is unordered.
Five records, one RRset. Same owner name, same class, same type. And look at the TTL column: 3600 on every single line, not by coincidence but because it is structurally impossible for them to differ.
Read one more thing out of this. The numbers 10, 20, 30 are inside the RDATA — they are part of the MX record's value, not a DNS-level priority. DNS itself has no concept of preference or ordering; the mail client parses those numbers. Every piece of "intelligence" you see in DNS lives in the RDATA and is interpreted by the client, never by DNS. Hold on to that, because it explains why DNS load balancing is as limited as it is.
Now imagine this at 500 hosts. An engineer adds a sixth mail server via the API by sending a single MX record. The API replaces the RRset. Five mail servers vanish from DNS in one call, and nothing errors.
🎯 Interview questions — Records and RRsets
Q. What is an RRset?
All records sharing the same owner name, class and type. DNS treats it as a single indivisible object — it is cached, transferred, replaced and DNSSEC-signed as a unit.
The practical rule that follows is that every record in an RRset must carry the same TTL, because they are cached as one object and expire together.
The detail that separates a strong candidate: knowing that this is why most DNS APIs are replace-the-whole-set rather than add-one-record, and why a naive automation script that sends a single value silently deletes the rest. It also explains DNSSEC: signatures cover RRsets, not individual records, which is why you cannot pick one address out of a signed set without breaking the signature.
Q. Can two records with the same name and type have different TTLs?
No. RFC 2181 requires the TTLs within an RRset to be identical. If a zone file specifies different values the server will choose one — typically the first or the lowest, depending on implementation — and discard the rest, usually with a warning you will never see because nobody reads zone-load logs.
Different types at the same name are different RRsets and may absolutely have different TTLs, which is why seamless.se can serve A at 60 seconds and MX at 3600 seconds simultaneously.
A2 · Asking for a type, and why ANY is a dead end
DNS is the same. There is no "show me everything" button — you ask for one type at a time, in a loop. ANY sounds like the button and never was.
In Module 01 every query was type A, because that is dig's default. You choose a different type by naming it:
dig seamless.se MX # type after the name
dig -t MX seamless.se # or with -t, identical
dig MX seamless.se # dig is relaxed about orderingEvery beginner then asks the obvious question: is there a way to get everything at once? There is a query type called ANY, and it does not do that.
Worse, it became the preferred tool of DNS amplification attacks, because a tiny query produced an enormous response. So in 2019 the major operators acted: Cloudflare returns a minimal synthetic answer, and RFC 8482 formally blessed refusing to give a conventional ANY response.
The operational conclusion: if you want to know every record type at a name, you must ask for each type in turn. There is no listing operation in the DNS protocol. This is not a limitation of your tools — it is a property of the protocol, and Part D is built entirely around the consequences.
🧪 Exercise A2.1 — Try ANY, watch it disappoint you, then do it properly
dig seamless.se ANY +noall +answer
echo "--- the honest way ---"
for t in SOA NS A AAAA MX TXT CAA; do dig +noall +answer seamless.se $t; done✅ Expected result — click to reveal
$ dig seamless.se ANY +noall +answer
seamless.se. 3600 IN HINFO "RFC8482" ""
--- the honest way ---
seamless.se. 900 IN SOA ns-1490.awsdns-58.org. awsdns-hostmaster.amazon.com. 1 7200 900 1209600 86400
seamless.se. 3600 IN NS ns-135.awsdns-16.com.
seamless.se. 3600 IN NS ns-1490.awsdns-58.org.
seamless.se. 3600 IN NS ns-1760.awsdns-28.co.uk.
seamless.se. 3600 IN NS ns-722.awsdns-26.net.
seamless.se. 60 IN A 52.77.52.233
seamless.se. 3600 IN MX 10 aspmx.l.google.com.
seamless.se. 3600 IN MX 20 alt1.aspmx.l.google.com.
seamless.se. 3600 IN MX 20 alt2.aspmx.l.google.com.
seamless.se. 3600 IN MX 30 aspmx2.googlemail.com.
seamless.se. 3600 IN MX 30 aspmx3.googlemail.com.
seamless.se. 300 IN TXT "v=spf1 include:amazonses.com include:_spf.linserv.se include:spf.mandrillapp.com include:_spf.salesforce.com a:mail.workbuster.se include:_spf.google.com ip4:3.1.214.242 ip4:14.142.43.230 ip4:14.99.30.2 ~all"ANY gave you a joke record. HINFO "RFC8482" is a deliberate placeholder meaning "I am not answering ANY queries; ask for a specific type". Depending on which resolver you hit you may instead get a random handful of records, or a REFUSED. None of those is an inventory.
The loop is the real answer, and notice what it revealed that a single query never could:
- Four NS records, all awsdns — this zone is hosted on AWS Route 53. That one line tells you where to go to make changes, and it tells you Route 53's quirks apply
- AAAA and CAA produced no output at all. Not an error — NODATA. This domain publishes no IPv6 address and no certificate-issuance policy. Both are findings
- The A record TTL is 60 while everything else is 3600. Somebody deliberately made the address agile. That is the signature of a host that gets repointed — a failover target, or a migration in progress
- The SPF record in TXT lists ip4: addresses. Those are mail-sending hosts, and they are a free hint about the organisation's own IP ranges. Part D uses this
Now imagine this at 500 domains. "Ask for each type in turn" is a loop, not a task — which is exactly why the audit script in D5 exists.
🎯 Interview questions — Query types
Q. How do you list all the records for a domain?
You cannot — and recognising that the question contains a false premise is the answer they are looking for. DNS has no listing operation. It answers questions of the form "what is at this exact name, of this exact type", and nothing else.
The practical approaches are: query each type you care about in turn; attempt a zone transfer if you are permitted one; or, most reliably, read the zone from the provider's API or console, because the provider does hold the full list.
The trap in this question is ANY. Candidates reach for it and it has never done what they think. It returns cached fragments, and since RFC 8482 most large operators return a minimal synthetic response instead — partly because ANY was the engine of DNS amplification attacks.
Part B · The record types you will actually meet
B1 · A and AAAA — addresses
If you only ever test with the old format, you will swear everything is fine while a growing group of people cannot reach you at all.
| Type | RDATA | Notes |
|---|---|---|
| A | One IPv4 address | Four bytes on the wire. AAAA is not "A for IPv6" in format, only in purpose |
| AAAA | One IPv6 address | Sixteen bytes — four times four, which is where the four As come from |
🧪 Exercise B1.1 — Ask both address types, and notice the absence
dig +noall +answer seamless.se A
dig +noall +comments seamless.se AAAA✅ Expected result — click to reveal
$ dig +noall +answer seamless.se A
seamless.se. 60 IN A 52.77.52.233
$ dig +noall +comments seamless.se AAAA
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 44118
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1The second one is the exercise. status: NOERROR with ANSWER: 0 — the NODATA case from Module 01 D2, met in the wild. The name exists, it has records, it has no IPv6 address.
Why this is a finding and not a curiosity. An IPv6-only client cannot reach this site at all without a translation layer, and a growing number of mobile networks are IPv6-only. "No AAAA" is a deliberate choice or an oversight, and the audit tells you which by whether anything else in the zone has one.
And note the TTL of 60 on the A record while the zone's other records sit at 3600. Somebody wanted to be able to move this address quickly. That is worth asking about — it usually means a failover mechanism or an unfinished migration.
B2 · CNAME — the alias, and the rule that makes it dangerous
That is the whole CNAME rule in one sentence. It redirects the address, not one kind of post. So a name with a CNAME can hold nothing else — and filing one on your main address is how a company's email quietly stops arriving.
A CNAME says: this name is an alias; the real name is that one. The resolver then starts again at the target.
So: a name with a CNAME cannot have any other record. Not an MX, not a TXT, not an A. There is nowhere to put them, because the name is not a destination any more — it is a signpost. You do not need to memorise that rule if you understand the sentence above; you can derive it.
This is why a CNAME at the zone apex is forbidden: the apex always carries SOA and NS records, so it can never be an alias. C3 covers what your provider offers instead.
🧪 Exercise B2.1 — Follow a real alias chain, then watch a CNAME swallow another type
# www is an alias. Watch dig return BOTH the alias and the resolved target.
dig +noall +answer www.seamless.se
# now ask that same alias for a completely different type
dig +noall +comments +answer www.seamless.se MX✅ Expected result — click to reveal
$ dig +noall +answer www.seamless.se
www.seamless.se. 3600 IN CNAME seamless.se.
seamless.se. 60 IN A 52.77.52.233
$ dig +noall +comments +answer www.seamless.se MX
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 30277
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
www.seamless.se. 3600 IN CNAME seamless.se.First command — two records came back for one question. You asked for A at www.seamless.se and got a CNAME plus the A at the target. The resolver followed the alias for you and returned the whole chain as evidence. Note the two different TTLs: 3600 on the alias, 60 on the address. They are different RRsets at different names, cached and expiring independently — which means the alias can outlive the address it points at.
Second command is the important one. You asked for MX and got a CNAME back with ANSWER: 1. The server is telling you: this name is an alias, go and ask the target instead. There is no MX record here and there never can be.
That is the whole danger in one screen. If www.seamless.se were the domain receiving mail, its mail would follow this alias to wherever the target points. A CNAME does not politely apply to the record type you were thinking about — it captures the name completely.
A practical tell for audits: any +short output whose first line is a hostname rather than an IP address is a CNAME chain. Chains of three or four are common with CDNs and SaaS vendors, each hop adds latency on a cache miss, and each hop is a third party who can break you.
🎯 Interview questions — CNAME
Q. What is a CNAME and what are its restrictions?
An alias: it declares that the canonical name of this name is some other name, and the resolver restarts its lookup there.
The restriction is that a name owning a CNAME can own no other record type, because the CNAME redirects the whole name rather than one record type. That in turn makes a CNAME illegal at the zone apex, since the apex must carry SOA and NS.
What I would add to show I understand the mechanism rather than the rule: the danger is silent. Adding a CNAME to a name that already has MX or TXT records does not usually produce an error — it produces mail that stops being delivered and domain-verification records that stop being found. And CNAMEs cost you a second resolution step, so long vendor chains add real latency on every cache miss.
Q. Why can't you put a CNAME at the apex of a zone, and what do you do instead?
Because the apex is required to carry SOA and NS records, and a CNAME excludes all other types at the same name. It is a structural contradiction, not a policy choice.
In practice you use a provider-specific pseudo-record: Route 53 alias records, or the ALIAS / ANAME / CNAME flattening offered by other providers. These are not DNS features — the authoritative server resolves the target internally and answers with a real A or AAAA, so what goes on the wire is a plain address record and standards compliance is preserved.
The operational catch worth naming: because the provider resolves the target for you, the answer is only as fresh as the provider's own resolution, and the flattening is done from the provider's network location. For a geo-distributed target that can hand your users a suboptimal address, and it is invisible unless you go looking.
B3 · NS — where authority is handed over
And the name on the branch's sign tells you something for free: which company actually runs it. That is why one NS lookup tells you the DNS provider, and therefore which console to log into.
An NS record names a server that is authoritative for a zone. In practice it is the record you look up first on any unfamiliar domain, because it answers two questions at once: who do I ask for a guaranteed-fresh answer, and who actually operates this domain's DNS.
🧪 Exercise B3.1 — Find the authoritative servers, then use one
dig +noall +answer seamless.se NS
# now ask one of them directly, and watch the header change
dig @ns-135.awsdns-16.com seamless.se A +noall +comments +answer✅ Expected result — click to reveal
$ dig +noall +answer seamless.se NS
seamless.se. 3600 IN NS ns-135.awsdns-16.com.
seamless.se. 3600 IN NS ns-722.awsdns-26.net.
seamless.se. 3600 IN NS ns-1490.awsdns-58.org.
seamless.se. 3600 IN NS ns-1760.awsdns-28.co.uk.
$ dig @ns-135.awsdns-16.com seamless.se A +noall +comments +answer
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 35503
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
seamless.se. 60 IN A 52.77.52.233Four findings from eight lines of output.
1. awsdns means Route 53. Someone hands you this domain and you now know where the records live without asking anyone.
2. Four different top-level domains — .com, .net, .org, .co.uk. That is deliberate, and it is good practice worth recognising. If one TLD's infrastructure has a bad day, three of your four nameservers are still reachable. Providers who put all four nameservers under one domain have quietly given you a shared failure mode.
3. The second query has aa set and no ra. Exactly as predicted in Module 01 B1: an authoritative server marks its answer authoritative and does not offer recursion. This is your ground truth — when you need to know what the zone actually says rather than what a cache remembers, this is the query.
4. The TTL is 60 here too, and it is the full published value rather than a counted-down one, which confirms it: the zone really does publish a 60-second address TTL.
The habit to build. Whenever you are verifying a change, or you suspect a cache, run the NS lookup and then re-run your query against one of the returned servers. Two commands, and you have removed every cache in the world from the investigation.
🎯 Interview questions — NS records
Q. What does an NS record do?
It names an authoritative server for a zone. NS records appear in two places: inside the zone itself at the apex, and — more importantly — in the parent zone, where they form the delegation.
Operationally it is the first lookup I run on an unfamiliar domain, because the nameserver hostnames identify the DNS provider, which tells me where to make changes and which provider-specific behaviours to expect.
The detail that matters and catches people out: the authoritative set is the one published by the parent, not the one inside the zone. The two are supposed to agree, and when they do not you get a delegation inconsistency that produces intermittent, resolver-dependent failures — the kind where the problem follows some users and not others. Module 03 covers how a resolver actually uses these.
B4 · SOA — the zone's control panel
The version number is the important bit. Everyone else decides whether to bother re-downloading by looking at that number — not by reading the document. Change the document and forget to change the number, and nobody ever gets your edit.
You have already met the SOA twice without being told what it was: it is the record that appeared in the AUTHORITY section of every negative answer in Module 01. Exactly one exists per zone, at the apex, and it holds seven fields.
seamless.se. 900 IN SOA ns-1490.awsdns-58.org. awsdns-hostmaster.amazon.com. 1 7200 900 1209600 86400
│ │ │ │ │ │ │
│ │ │ │ │ │ └─ MINIMUM 86400
│ │ │ │ │ └───────── EXPIRE 1209600
│ │ │ │ └───────────── RETRY 900
│ │ │ └────────────────── REFRESH 7200
│ │ └──────────────────── SERIAL 1
│ └────────────────────────────────────────────────── RNAME
└───────────────────────────────────────────────────────────────────────── MNAME| Field | What it is for |
|---|---|
| MNAME | The primary nameserver — where changes are made. Secondaries send dynamic updates here |
| RNAME | The responsible party's email address, written as a domain name. The first dot is the @, so awsdns-hostmaster.amazon.com. means [email protected] |
| SERIAL | A version number. A secondary transfers the zone only when the primary's serial is higher than its own |
| REFRESH | How often a secondary checks the primary's serial, absent a NOTIFY |
| RETRY | How soon to retry after a failed check |
| EXPIRE | How long a secondary keeps serving the zone while it cannot reach the primary. After this it stops answering entirely — which is safer than serving data of unknown age |
| MINIMUM | Originally a default TTL. Since RFC 2308 it means the negative caching TTL — how long a "this does not exist" may be cached |
And there is a precision here that most write-ups get wrong. RFC 2308 says the negative TTL is the lesser of the SOA's MINIMUM field and the SOA record's own TTL. For seamless.se that is min(86400, 900) = 900 seconds, not 86400. Knowing which of the two numbers actually governs is the difference between predicting a 15-minute wait and a 24-hour one.
Route 53 does not work that way: it replicates internally rather than by AXFR, so the serial is decorative. The lesson is not "serials do not matter" — it is "serials matter exactly as much as your replication mechanism depends on them". On BIND primary/secondary, forgetting to bump the serial is the classic "I changed it and only some servers agree" outage. Module 05 makes you commit it deliberately.
🧪 Exercise B4.1 — Read a real SOA and compute the negative cache time yourself
dig +noall +answer seamless.se SOA
# now prove the negative TTL is real: ask for a name that does not exist,
# twice, and watch the SOA's TTL count down in the AUTHORITY section
dig no-such-name.seamless.se +noall +authority
sleep 10
dig no-such-name.seamless.se +noall +authority✅ Expected result — click to reveal
$ dig +noall +answer seamless.se SOA
seamless.se. 900 IN SOA ns-1490.awsdns-58.org. awsdns-hostmaster.amazon.com. 1 7200 900 1209600 86400
$ dig no-such-name.seamless.se +noall +authority
seamless.se. 900 IN SOA ns-1490.awsdns-58.org. awsdns-hostmaster.amazon.com. 1 7200 900 1209600 86400
$ sleep 10 ; dig no-such-name.seamless.se +noall +authority
seamless.se. 890 IN SOA ns-1490.awsdns-58.org. awsdns-hostmaster.amazon.com. 1 7200 900 1209600 86400You are watching a "no" expire. The TTL went 900 → 890 across a 10-second sleep, exactly like a positive answer would. The resolver has cached the non-existence of no-such-name.seamless.se and is counting it down.
Do the arithmetic that matters. MINIMUM is 86400 — a full day. The SOA record's own TTL is 900. RFC 2308 takes the lesser, so a negative answer for this zone lives 15 minutes, not 24 hours. If you had read only the MINIMUM field you would have told your team to wait a day, and been wrong by a factor of 96.
The operational rule this gives you. After creating a new name, if you get NXDOMAIN, do not repeat the query against the same resolver and do not panic. Query an authoritative server — from B3 — which holds no negative cache. If it has the record, your change landed and you are simply waiting out min(MINIMUM, SOA TTL).
And the rule that prevents the problem entirely: never query a name before you publish it. The NXDOMAIN you cache is your own doing. This is why deployment scripts that "check if the record exists yet" in a tight loop make the wait longer on every resolver they touch.
🎯 Interview questions — SOA
Q. Walk me through the SOA record.
Seven fields: MNAME, the primary nameserver; RNAME, the responsible email address written as a domain name with the first dot standing in for the @; SERIAL, a version number; then REFRESH, RETRY and EXPIRE, which are the secondary's timers; and MINIMUM, which since RFC 2308 is the negative caching TTL.
EXPIRE is the one worth calling out: when a secondary has been unable to reach the primary for that long it stops answering for the zone entirely, rather than continuing to serve data of unknown age. That is a deliberate choice of a hard failure over a silent one.
Where a strong candidate goes further: the negative TTL is not simply the MINIMUM field — it is the lesser of MINIMUM and the SOA record's own TTL. That distinction decides whether a freshly created record is invisible for fifteen minutes or for a day, and almost every blog post gets it wrong.
Q. Why does the serial number matter, and what happens if you forget to increment it?
Secondaries compare the primary's serial with their own and transfer only when it is higher. If you edit a zone and do not bump the serial, the primary serves the new data and every secondary keeps serving the old — so answers depend on which server a resolver happened to ask.
That produces the worst class of DNS bug: intermittent, user-dependent, and not reproducible on demand.
The nuance worth adding: this only applies where replication is serial-driven, as in BIND primary/secondary. Managed providers such as Route 53 replicate internally and leave the serial at a constant — Route 53 publishes 1 and never changes it. So the right instinct is not "always bump the serial" but "know whether your replication depends on it", and the SOA tells you which world you are in.
B5 · MX — mail routing
The preference numbers are simply "try the main mail room first; if it is shut, use the back-up one; if that is shut too, try the third".
An MX record has two parts in its RDATA: a preference number and a hostname. Lower preference wins; equal preferences are load-shared.
1. An MX target must be a hostname with an address record — never an IP address, and never a CNAME. MX 10 192.0.2.5 is invalid; so is an MX pointing at an alias. Many servers accept both and some senders will still deliver, which is worse than a clean rejection, because the failure is partial and blamed on the recipient.
2. The MX lives on the domain in the email address, not on the web server. Mail for [email protected] follows the MX of seamless.se — which is why a CNAME at that apex would destroy mail delivery, exactly as B2 warned.
🧪 Exercise B5.1 — Read a real mail configuration and follow it one hop further
dig +noall +answer seamless.se MX
dig +short aspmx.l.google.com A
# a domain that publishes it accepts NO mail at all
dig +noall +answer example.com MX✅ Expected result — click to reveal
$ dig +noall +answer seamless.se MX
seamless.se. 3600 IN MX 10 aspmx.l.google.com.
seamless.se. 3600 IN MX 20 alt1.aspmx.l.google.com.
seamless.se. 3600 IN MX 20 alt2.aspmx.l.google.com.
seamless.se. 3600 IN MX 30 aspmx2.googlemail.com.
seamless.se. 3600 IN MX 30 aspmx3.googlemail.com.
$ dig +short aspmx.l.google.com A
64.233.181.26
$ dig +noall +answer example.com MX
example.com. 86400 IN MX 0 .The first output is a complete mail architecture in five lines. aspmx.l.google.com is Google Workspace, so this organisation's mail is hosted by Google. Preference 10 is the primary pair-of-hands, 20 is the fallback, 30 is the fallback's fallback. A sender walks up that list.
Note the two records at preference 20. Equal preference means the sender picks between them freely — this is deliberate load sharing, and it is the only place in core DNS where a weighting-like behaviour exists at all.
The second command is the habit worth forming. An MX target is only useful if it resolves. Following it one hop confirms the chain is intact; an MX pointing at a hostname with no address record is a silent mail outage that no MX-level check would catch.
The third output is the interesting one. MX 0 . is the null MX from RFC 7505 — a single MX whose target is the root. It is not a mistake or an empty value; it is an explicit, machine-readable declaration: this domain accepts no mail, do not queue and retry for five days, reject immediately.
Why that matters in production. Most organisations own domains that never receive mail — parked brands, redirect domains, internal-only names. Without a null MX, a sender falls back to the domain's A record for delivery, spends days retrying against a web server, and generates support tickets. One record removes the whole class of problem. It is also a small anti-spoofing win: a domain that visibly accepts no mail is a less attractive forgery target.
B6 · TXT — SPF, DKIM, DMARC and proof of ownership
It is a general-purpose noticeboard, which is exactly why so many different systems ended up using it.
TXT holds free-form text, which is precisely why it became the dumping ground for every protocol that needed to attach data to a name. Four uses dominate:
| Use | Where it lives | What it declares |
|---|---|---|
| SPF | The domain itself | Which servers may send mail as this domain |
| DKIM | <selector>._domainkey.<domain> | A public key used to verify message signatures |
| DMARC | _dmarc.<domain> | What a receiver should do when SPF and DKIM fail, and where to send reports |
| Ownership proofs | The domain, or _acme-challenge | "I control this domain" — for Google, Microsoft, Atlassian, and for Let's Encrypt DNS-01 |
🧪 Exercise B6.1 — Read a real mail-security posture
dig +short seamless.se TXT
dig +short _dmarc.seamless.se TXT✅ Expected result — click to reveal
$ dig +short seamless.se TXT
"v=spf1 include:amazonses.com include:_spf.linserv.se include:spf.mandrillapp.com include:_spf.salesforce.com a:mail.workbuster.se include:_spf.google.com ip4:3.1.214.242 ip4:14.142.43.230 ip4:14.99.30.2 ~all"
$ dig +short _dmarc.seamless.se TXT
"v=DMARC1; p=reject; pct=25; rua=mailto:[email protected]"The SPF record is an org chart. Six include: mechanisms name six third parties permitted to send mail as this domain — Amazon SES, a Swedish hosting provider, Mandrill, Salesforce, a recruitment platform, and Google Workspace. Each one is a vendor relationship and each one is a party who can send mail as you.
~all is a soft fail, meaning "mail from anywhere else is suspicious but do not reject it outright". -all would be a hard fail. Soft fail is the cautious setting people adopt during rollout and then never tighten.
Now read the DMARC record together with it, because that is where the real posture is. p=reject is the strongest policy — but pct=25 means it is applied to only 25% of failing mail. That combination is the signature of a staged rollout in progress: the operator is ramping up and watching the rua= aggregate reports before going to 100%.
The finding to write down. p=reject; pct=25 reads as "strict" at a glance and is enforcing on a quarter of traffic. If nobody owns finishing the ramp, it sits there for years and everyone believes the domain is protected. Reading pct= is what separates an auditor from someone skimming for the word reject.
Two operational cautions on SPF. There is a hard limit of 10 DNS lookups when evaluating a record, and every include: costs at least one — six includes plus an a: is already close to the ceiling. Exceed it and the result is permerror, which many receivers treat as a failure: your legitimate mail starts bouncing with no change on your side, because a vendor expanded their record. Second, a single string in a TXT record cannot exceed 255 characters, so long records are split into several strings that are concatenated — which is why quoting matters when you write them.
🎯 Interview questions — TXT, SPF and DMARC
Q. How are SPF, DKIM and DMARC represented in DNS?
All three are TXT records. SPF sits on the domain itself and lists the hosts permitted to send as it. DKIM sits at <selector>._domainkey.<domain> and publishes a public key that receivers use to verify a signature in the message header. DMARC sits at _dmarc.<domain> and states what to do when the other two fail, plus where to send reports.
The underscore prefixes are deliberate: underscores are legal in DNS names but not in host names, so these can never collide with a real machine.
The operational detail that marks experience: SPF has a hard limit of ten DNS lookups during evaluation, and each include: consumes at least one. Since includes point at vendors' records, a vendor expanding theirs can push you over the limit and start bouncing your mail with no change on your side. Anyone who has run mail for a large organisation has been bitten by this, and mentioning it lands.
Q. A domain publishes p=reject in DMARC. Is its mail protected?
Not necessarily — you have to read the pct= tag alongside it. p=reject; pct=25 applies the reject policy to only a quarter of failing messages, which is a rollout in progress rather than a finished deployment.
I would also check the alignment mode and whether rua= reports are actually going somewhere a human reads.
The point I would make explicitly: p=reject is the tag people screenshot for the compliance evidence, and pct= is the tag that says what is really enforced. A staged rollout nobody ever finished looks identical to a completed one unless you read the second tag.
B7 · PTR — reverse DNS
They are supposed to match, and nothing forces them to. When a post office refuses to accept mail from a building with no name plate on it, that is exactly what a receiving mail server does to a host with no PTR record.
Reverse DNS answers "which name belongs to this address", and it works by a trick: the address is turned into a name inside a special zone, and then looked up like anything else.
8.8.8.8 -> 8.8.8.8.in-addr.arpa. octets reversed, because DNS
reads most-specific-firstdig -x 8.8.8.8 builds that name for you.
So you cannot create a PTR for a public IP you rent, unless the provider gives you a control for it. And there is no requirement that they agree: A and PTR can point at completely different things, both perfectly valid.
🧪 Exercise B7.1 — Reverse three addresses, including one that has nothing
dig +short -x 8.8.8.8
dig +short -x 52.77.52.233
dig +short -x 14.99.30.4✅ Expected result — click to reveal
$ dig +short -x 8.8.8.8
dns.google.
$ dig +short -x 52.77.52.233
ec2-52-77-52-233.ap-southeast-1.compute.amazonaws.com.
$ dig +short -x 14.99.30.4
(no output)Each of the three tells you something different, and none of them is about the domain you started from.
dns.google. — a deliberately curated PTR. Someone chose that name.
ec2-52-77-52-233.ap-southeast-1.compute.amazonaws.com. — an AWS default, and it leaks two facts for free: this is an EC2 instance, and it is in ap-southeast-1, Singapore. You learned the hosting provider and the region without any access to the account. Reverse DNS is one of the most under-used reconnaissance surfaces there is, and Part D uses it deliberately.
14.99.30.4 returns nothing at all — no PTR exists. Common for on-premises and colocated ranges where nobody configured the reverse zone.
Why the empty one is a real finding rather than cosmetic. Receiving mail servers routinely check that a sending IP has a PTR, and that the PTR resolves forward to the same address — the forward-confirmed reverse DNS check. A host with no PTR that tries to send mail gets a deliverability penalty or an outright rejection. If any of the SPF-listed sending ranges from B6 lack PTRs, that is a mail-deliverability problem waiting to be blamed on something else.
B8 · SRV — service discovery
SRV gives you the building and the counter — it is the only common record that carries a port number. Which is why a service using it can move to a different counter without telling every visitor.
A tells you where a host is. SRV tells you where a service is, including its port — the only core record type that carries one.
_xmpp-client._tcp.jabber.org. 60 IN SRV 30 30 5222 scarlet.jabber.org.
│ │ │ │ │ └── target host
│ │ │ │ └──────── port
│ │ │ └──────────────── weight
│ │ └────────────────────── priority
│ └─────────────────────────────────────────────────────── protocol, _tcp or _udp
└──────────────────────────────────────────────────────────────────── service nameThe catch, and it is a big one: the client has to implement it. Browsers do not use SRV for HTTP at all. Where SRV does get used properly — Kubernetes headless services, XMPP, SIP, Active Directory, Minecraft — it works well, and Module 08 shows Kubernetes generating these automatically for every named port.
🧪 Exercise B8.1 — Read a live SRV record
dig +noall +answer _xmpp-client._tcp.jabber.org SRV✅ Expected result — click to reveal
_xmpp-client._tcp.jabber.org. 60 IN SRV 30 30 5222 scarlet.jabber.org.Priority 30, weight 30, port 5222, target scarlet.jabber.org. A client that speaks XMPP needs no configuration beyond the domain name — it constructs _xmpp-client._tcp.jabber.org, and DNS hands back the host and the port.
Compare that with how HTTP works. There is no SRV for the web; the port is a convention baked into the scheme, 80 or 443. That is why moving a web service to a different port requires telling every client, while moving an XMPP or SIP service is a DNS change. It is a real architectural difference and it explains why service meshes and Kubernetes lean on SRV so heavily.
The naming rule to remember, because you will construct these by hand in Module 08: _service._proto.name, both underscore-prefixed, protocol is _tcp or _udp.
B9 · CAA — who is allowed to issue your certificates
With no note on file, every solicitor in the world qualifies. That is the default state of any domain without a CAA record.
A CAA record names the certificate authorities permitted to issue certificates for your domain. Certificate authorities are required to check it at issuance time.
It climbs the tree. A CA checking api.eng.example.com looks there, then at eng.example.com, then example.com, and uses the first CAA set it finds. So one record at the apex protects the whole zone.
🧪 Exercise B9.1 — Compare a domain that has CAA with one that does not
dig +noall +answer google.com CAA
dig +noall +comments seamless.se CAA✅ Expected result — click to reveal
$ dig +noall +answer google.com CAA
google.com. 1341 IN CAA 0 issue "pki.goog"
$ dig +noall +comments seamless.se CAA
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 12903
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1Google's record says: only pki.goog may issue for this domain. The leading 0 is the flags byte; issue is the property tag.
seamless.se returns NODATA — no CAA record at all. Read that precisely, because the two readings are very different:
- It does not mean certificate issuance is blocked
- It means every publicly trusted CA in the world is permitted to issue for this domain, which is the default when no CAA record exists
This is a finding on nearly every domain audit, and it is one of the cheapest security improvements available: a single record, no user-visible change, no risk of breaking traffic, and it removes hundreds of organisations from the set of people who can mint a valid certificate for you.
The one way it bites you, and it is worth stating so you set it up correctly: add a CAA that names only your current CA, then later try to move to a different one, and the new CA will refuse to issue. The failure appears at renewal time, which is often 3am on a weekend. Add every CA you actually use, including the one your CDN or load balancer provisions certificates through, and use the iodef tag so violations are reported to you rather than silently dropped.
🎯 Interview questions — Record types
Q. Name the record types you use regularly and what each is for.
A and AAAA for IPv4 and IPv6 addresses. CNAME for aliases. NS for delegation. SOA for zone metadata and the negative caching TTL. MX for mail routing. TXT for SPF, DKIM, DMARC and ownership proofs. PTR for reverse lookups. SRV for service discovery including a port. CAA for certificate issuance policy.
What makes this answer stand out is grouping rather than listing. Three of them — NS, SOA and the delegation records — describe the zone itself. Three describe hosts. Three are policy records that no packet ever depends on but which break things badly when wrong: TXT for mail authentication, CAA for certificates, PTR for mail reputation. Reciting nine types is memorisation; naming three categories shows you understand what DNS is being used for.
Q. Which record type carries a port number?
SRV, and it is the only common one that does. Its RDATA is priority, weight, port and target, and the name is structured as _service._proto.name.
Priority works like MX preference — lowest wins — and weight distributes proportionally among equal priorities, which makes SRV the only core record type with genuine weighted load balancing.
The caveat that shows real-world experience: it only works if the client implements it, and most do not. Browsers ignore SRV entirely for HTTP. Where it is genuinely load-bearing is Kubernetes headless services, SIP, XMPP and Active Directory.
Part C · Zone files and wildcards
C1 · The zone file format
Helpful — until you write out the full address by habit and the form appends the city again. You end up with something that looks perfectly reasonable, passes every check, and points nowhere.
A zone file is the text representation of a zone. Managed providers hide it behind a web form, but the format is what their API is a wrapper around, and it is what BIND reads directly in Module 05. Four constructs do all the work.
| Construct | Meaning |
|---|---|
| $ORIGIN name. | The suffix appended to every relative name below this line. Usually the zone name |
| $TTL seconds | The default TTL for records that do not state one. Required, or you get warnings and a guess |
| @ | Shorthand for the current $ORIGIN — i.e. the zone apex |
| Blank owner name | A record whose first column is whitespace inherits the previous record's owner name |
So writing www.example.com — which looks completely correct — inside the example.com zone produces www.example.com.example.com.
The file loads. There is no error. Nothing warns you. You get a valid zone containing a name nobody will ever query, and the name you meant to create simply does not exist.
The habit that removes the problem entirely: write www, or write www.example.com. with the dot. Never write the half-qualified middle form.
Here is a complete, valid zone with every construct in it:
$TTL 3600
$ORIGIN example.internal.
@ IN SOA ns1.example.internal. hostmaster.example.internal. (
2026081701 ; serial
7200 ; refresh
900 ; retry
1209600 ; expire
900 ) ; minimum - negative TTL
@ IN NS ns1.example.internal.
@ IN NS ns2.example.internal.
ns1 IN A 192.0.2.10
ns2 IN A 192.0.2.11
@ IN A 192.0.2.20
www IN CNAME @
api IN A 192.0.2.30
api IN A 192.0.2.31
@ IN MX 10 mail.example.internal.
mail IN A 192.0.2.40
* IN A 192.0.2.99Read three things out of that file before you run anything. api has two A records — that is one RRset with two members, exactly as A1 described, and they will share the $TTL of 3600. www is a CNAME to @, which is the apex — legal, because the alias is at www, not at the apex. And the last line begins with *, which is a wildcard; C4 is entirely about why that line is more dangerous than it looks.
C2 · Writing and validating a zone by hand
named-checkzone is spell-check. The -D dump is the proofreader — it reads the file back to you as the server understood it, so you can see what you actually said.
You do not need a running nameserver to check a zone file. named-checkzone parses it exactly as named would.
sudo apt install -y bind9-utils # Debian / Ubuntu
sudo dnf install -y bind-utils # RHEL / Rocky / Fedora🧪 Exercise C2.1 — Write the zone, validate it, then read it back as the server sees it
mkdir -p ~/dns-lab && cd ~/dns-lab
# paste the zone from C1 into db.example.internal, then:
named-checkzone example.internal db.example.internal
# -D dumps the zone in canonical form: every name fully qualified,
# every TTL explicit, every shorthand expanded
named-checkzone -D example.internal db.example.internal✅ Expected result — click to reveal
$ named-checkzone example.internal db.example.internal
zone example.internal/IN: loaded serial 2026081701
OK
$ named-checkzone -D example.internal db.example.internal
zone example.internal/IN: loaded serial 2026081701
example.internal. 3600 IN SOA ns1.example.internal. hostmaster.example.internal. 2026081701 7200 900 1209600 900
example.internal. 3600 IN NS ns1.example.internal.
example.internal. 3600 IN NS ns2.example.internal.
example.internal. 3600 IN A 192.0.2.20
example.internal. 3600 IN MX 10 mail.example.internal.
*.example.internal. 3600 IN A 192.0.2.99
api.example.internal. 3600 IN A 192.0.2.30
api.example.internal. 3600 IN A 192.0.2.31
mail.example.internal. 3600 IN A 192.0.2.40
ns1.example.internal. 3600 IN A 192.0.2.10
ns2.example.internal. 3600 IN A 192.0.2.11
www.example.internal. 3600 IN CNAME example.internal.
OK-D is the most under-used debugging tool in DNS and you should build a habit around it. It shows you the zone as the server understands it, with every @ expanded, every relative name qualified, and every inherited TTL made explicit.
Which means every ambiguity in your file becomes visible. www IN CNAME @ became www.example.internal. CNAME example.internal. — you can see with your own eyes that the @ resolved to what you intended. And if you had made the trailing-dot mistake, you would see the doubled name sitting right there in the output instead of discovering it from a user report.
The workflow to adopt: edit, named-checkzone, then named-checkzone -D and actually read it. OK only tells you the file parses. The dump tells you it means what you meant.
C3 · CNAME at the apex, and what your provider does instead
So providers offer a workaround: they quietly do the forwarding behind the counter and hand out a normal address, so from the outside nothing unusual is happening at all.
B2 told you the rule and the reason. Now watch the server enforce it.
🧪 Exercise C3.1 — Put a CNAME at the apex and get the zone rejected
cd ~/dns-lab
# replace the apex A record with a CNAME
sed 's|^@ IN A 192.0.2.20|@ IN CNAME cdn.provider.net.|' \
db.example.internal > broken-apex.zone
named-checkzone example.internal broken-apex.zone✅ Expected result — click to reveal
dns_master_load: broken-apex.zone:13: example.internal: CNAME and other data
dns_master_load: broken-apex.zone:13: example.internal: CNAME and other data
dns_master_load: broken-apex.zone:17: example.internal: CNAME and other data
zone example.internal/IN: loading from master file broken-apex.zone failed: CNAME and other data
zone example.internal/IN: not loaded due to errors.CNAME and other data — memorise that string. It is the error you will meet whenever a name has a CNAME plus anything else, and BIND reports it three times because three separate records collide with the alias: the two NS records at line 13, and the MX at line 17.
The zone is not loaded. This is a good failure: loud, immediate, and before anything is served. Compare it with the failures in C5 that load cleanly and break in production.
Now the useful part — what you actually do when a vendor says "point your root domain at this hostname". You cannot use a CNAME, so every provider offers a non-standard pseudo-record:
| Provider | What it is called |
|---|---|
| Route 53 | alias record |
| Cloudflare | CNAME flattening |
| DNSimple, easyDNS | ALIAS |
| DNS Made Easy, others | ANAME |
All of them work the same way, and understanding the mechanism tells you the trade-off. They are not on the wire. The authoritative server resolves the target internally and answers your query with a genuine A or AAAA record. Standards compliance is preserved because a normal address record is what leaves the server.
The two consequences that bite. First, the flattening happens from the provider's network position, not yours — so if the target is geo-distributed, your users may get an address chosen for the wrong region. Second, these records are provider-specific and cannot be transferred, so migrating DNS providers means re-creating every one of them by hand, and forgetting one is an apex outage.
C4 · Wildcards
You have quietly given up your ability to detect mistakes. Every typo now succeeds, and every check that relied on "this name does not exist" is broken forever.
A record whose owner name is * matches any name at that level that does not otherwise exist.
- It only fires when nothing else matches. If api.example.com exists, the wildcard is never consulted for it — not even for a record type the real name lacks. Existence of the name blocks the wildcard, not existence of the record type
- It matches one level only. *.example.com matches foo.example.com but not foo.bar.example.com
- It is type-specific. * IN A answers A queries. A query for MX at a name covered by that wildcard returns NODATA, not the wildcard
- The name in the answer is the queried name, not *. Nothing in the response reveals that a wildcard produced it
You lose your single most reliable signal — "this name does not exist" — for every name in the zone. Monitoring that checks "does this record resolve" becomes worthless, and Part D's enumeration techniques stop working entirely against that zone.
🧪 Exercise C4.1 — Prove all four properties in one pass
cd ~/dns-lab
named-checkzone -D example.internal db.example.internal | grep -E '^\*|^api|^www'Then reason it through before opening the toggle. In the zone from C1, which of these return the wildcard address 192.0.2.99?
1. anything.example.internal A
2. api.example.internal A
3. api.example.internal MX
4. deep.nested.example.internal A
5. www.example.internal A✅ Expected result — click to reveal
*.example.internal. 3600 IN A 192.0.2.99
api.example.internal. 3600 IN A 192.0.2.30
api.example.internal. 3600 IN A 192.0.2.31
www.example.internal. 3600 IN CNAME example.internal.1. anything.example.internal → 192.0.2.99. YES. No such name exists, so the wildcard fires. This is the case everyone expects.
2. api.example.internal → 192.0.2.30 and .31. NO. The name exists with its own A records, so the wildcard is never reached.
3. api.example.internal MX → NODATA. NO — and this is the one that catches people. api has no MX record, so you might expect the wildcard to supply one. It does not. The name api exists, and existence blocks the wildcard for every type. You get NOERROR with ANSWER: 0. Property 1, stated precisely.
4. deep.nested.example.internal → NXDOMAIN. NO. *.example.internal covers one label only. A wildcard does not descend, and *.*.example.internal is not a thing.
5. www.example.internal → follows the CNAME to the apex, 192.0.2.20. NO. www exists as an alias, and that is enough to block the wildcard.
The mental model that gets all five right without memorising anything: the resolver looks for the exact name first. If the name exists in any form at all, the wildcard is out of the picture — regardless of which type you asked for. Only when the name is entirely absent does the wildcard get consulted, and then only for the type you asked for, and only one level down.
Now imagine this at 500 hosts. Someone adds *.example.com A pointing at a marketing landing page. Every mistyped internal hostname now resolves to the marketing site. Health checks that relied on NXDOMAIN to detect a decommissioned service go green forever. And the zone becomes un-auditable, because you can no longer distinguish a name that exists from one that does not.
C5 · The mistakes that load cleanly and still break
The errors that cost you a weekend are never the ones that get rejected at the counter.
C3's failure was the easy kind: loud, immediate, nothing served. These three are the dangerous kind. All three produce a zone that loads successfully.
🧪 Exercise C5.1 — Commit three silent errors on purpose and see how quiet they are
cd ~/dns-lab
echo "===== 1. two TTLs in one RRset ====="
sed 's|^api IN A 192.0.2.31|api 60 IN A 192.0.2.31|' \
db.example.internal > b-ttl.zone
named-checkzone example.internal b-ttl.zone
echo "===== 2. MX pointing at a CNAME ====="
sed 's|^@ IN MX 10 mail.example.internal.|@ IN MX 10 www.example.internal.|' \
db.example.internal > b-mx.zone
named-checkzone example.internal b-mx.zone
echo "===== 3. the missing trailing dot ====="
sed 's|^www IN CNAME @|www IN CNAME www.example.internal|' \
db.example.internal > b-dot.zone
named-checkzone -D example.internal b-dot.zone | grep www✅ Expected result — click to reveal
===== 1. two TTLs in one RRset =====
b-ttl.zone:16: TTL set to prior TTL (3600)
zone example.internal/IN: loaded serial 2026081701
OK
===== 2. MX pointing at a CNAME =====
zone example.internal/IN: example.internal/MX 'www.example.internal' is a CNAME (illegal)
zone example.internal/IN: loaded serial 2026081701
OK
===== 3. the missing trailing dot =====
www.example.internal. 3600 IN CNAME www.example.internal.example.internal.Look at the last line of the first two: OK. Both zones loaded and would be served.
Error 1 — the TTL you wrote was thrown away. You asked for 60 seconds on one address; BIND silently forced it back to 3600 because RFC 2181 forbids differing TTLs inside an RRset. One line of warning in a log nobody reads, and then it serves happily. If that 60-second TTL was your failover plan, your failover plan is now an hour long and you have no idea.
Error 2 — is a CNAME (illegal), followed by OK. The zone loads with a mail configuration that is invalid per RFC 2181. Some senders will follow it, some will refuse. You get partial mail delivery, which is far harder to diagnose than none at all, because the people who can reach you have no reason to report anything.
Error 3 — no warning whatsoever, and the damage is right there in the dump. www.example.internal. is now an alias for www.example.internal.example.internal., a name that does not exist. named-checkzone said OK because the file is syntactically perfect. This is the trailing-dot bug from Module 01 A3, and it is the reason C2 tells you to read the -D dump rather than trust the word OK.
The general lesson, which is worth more than the three specifics. In DNS, OK means "this parses", not "this is correct". The failures that cost you a weekend are never syntax errors — they are semantically valid files that say something you did not mean. That is why the verification step is always query the result, never the file loaded.
🎯 Interview questions — Zone files
Q. What happens if you forget the trailing dot in a zone file?
The zone's $ORIGIN is appended, so www.example.com written inside the example.com zone becomes www.example.com.example.com.
The file is syntactically valid, so it loads without error and named-checkzone reports OK. You get a working zone containing a name nobody will ever query, and the name you intended does not exist.
How I avoid it, which is the practical half of the answer: write names either fully relative — www — or fully qualified with the dot, and never the half-qualified middle form. Then verify with named-checkzone -D, which dumps every name fully expanded so the doubled name is visible immediately rather than discovered from a user report.
Q. A colleague adds *.example.com A 192.0.2.99. What are your concerns?
The main one is that NXDOMAIN disappears from the entire zone. Every conceivable name now resolves, so typos succeed, decommissioned services keep answering, and any monitoring that relies on a name failing to resolve goes permanently green.
I would also check what it shadows and what it does not: the wildcard only fires for names that do not otherwise exist, matches a single label, and is type-specific — so api.example.com MX still returns NODATA if api exists with only an A record.
And the operational point I would raise in the review: it makes the zone un-auditable. You can no longer answer "does this name exist" for anything under the domain, which breaks inventory, breaks certificate scoping, and breaks the enumeration techniques you would otherwise use to find out what you own.
Part D · Field recipes — a domain you have been handed
Every command here has been run against seamless.se and the outputs are real. Substitute your own domain and the recipes work unchanged.
D1 · What records does this one name have?
There is no listing operation, so "what records does this name have" is always a loop over types. There are three ways to write that loop and it is worth knowing all three.
DOMAIN=seamless.se
# 1. the shell loop - clearest, and lets you label each type
for t in SOA NS A AAAA MX TXT CAA SRV DNSKEY; do
dig +noall +answer "$DOMAIN" "$t"
done
# 2. several queries in ONE dig invocation - fewer processes
dig +noall +answer $DOMAIN A $DOMAIN MX $DOMAIN TXT $DOMAIN NS
# 3. batch mode from a file - best for many names at once
printf '%s A\n%s MX\n%s NS\n' $DOMAIN $DOMAIN $DOMAIN > /tmp/q.txt
dig -f /tmp/q.txt +noall +answer🧪 Exercise D1.1 — Run the full sweep on a real domain and read the findings
DOMAIN=seamless.se
for t in SOA NS A AAAA MX TXT CAA DNSKEY; do
out=$(dig +noall +answer "$DOMAIN" "$t")
[ -n "$out" ] && echo "$out" || printf '%-8s (none)\n' "$t"
done✅ Expected result — click to reveal
seamless.se. 900 IN SOA ns-1490.awsdns-58.org. awsdns-hostmaster.amazon.com. 1 7200 900 1209600 86400
seamless.se. 3600 IN NS ns-135.awsdns-16.com.
seamless.se. 3600 IN NS ns-1490.awsdns-58.org.
seamless.se. 3600 IN NS ns-1760.awsdns-28.co.uk.
seamless.se. 3600 IN NS ns-722.awsdns-26.net.
seamless.se. 60 IN A 52.77.52.233
AAAA (none)
seamless.se. 3600 IN MX 10 aspmx.l.google.com.
seamless.se. 3600 IN MX 20 alt1.aspmx.l.google.com.
seamless.se. 3600 IN MX 20 alt2.aspmx.l.google.com.
seamless.se. 3600 IN MX 30 aspmx2.googlemail.com.
seamless.se. 3600 IN MX 30 aspmx3.googlemail.com.
seamless.se. 300 IN TXT "v=spf1 include:amazonses.com include:_spf.linserv.se include:spf.mandrillapp.com include:_spf.salesforce.com a:mail.workbuster.se include:_spf.google.com ip4:3.1.214.242 ip4:14.142.43.230 ip4:14.99.30.2 ~all"
CAA (none)
DNSKEY (none)The (none) lines are worth as much as the records, and this is the habit to build: print the absences explicitly. A sweep that only shows what exists lets you skim past three findings.
The seven-line briefing you can now give, without asking anyone anything:
- DNS is on AWS Route 53 — the awsdns nameservers. You know which console and which API
- Mail is Google Workspace — aspmx.l.google.com, with a proper three-tier preference ladder
- No IPv6. IPv6-only clients cannot reach this name
- No CAA — every publicly trusted CA in the world may issue certificates for this domain
- No DNSKEY — the zone is not DNSSEC-signed. That matters practically in D3: it rules out one enumeration technique entirely
- The A TTL is 60 while everything else is 3600 — this address is designed to be moved quickly. Ask why
- The SPF record names six external senders and three ip4: addresses. Six vendor relationships, and three IPs that belong to the organisation's own mail infrastructure — keep those, D4 uses them
Now imagine this at 500 domains. This is exactly why it becomes the script in D5 rather than something you type.
D2 · Does this specific name exist?
A resolver is the colleague; the authoritative server is HR. And before you trust any answer, check the company is not one of those places where reception says "yes, they work here" about absolutely everybody — that is the wildcard check.
This is your admin-vodafone.seamless.se question, and it has a precise procedure — because "it didn't return an IP" has four different meanings, and only one of them means the name is absent.
Diagram source
flowchart TD
Q["dig name +noall +comments"] --> S{"read status:"}
S -->|"NOERROR<br>ANSWER greater than 0"| E["EXISTS<br>and has this record type"]
S -->|"NOERROR<br>ANSWER 0"| N["EXISTS<br>but no record of THIS type<br>try A, AAAA, CNAME, TXT"]
S -->|"NXDOMAIN"| X{"does the zone<br>have a wildcard?"}
S -->|"SERVFAIL or REFUSED"| U["UNKNOWN<br>you learned nothing<br>ask another resolver"]
X -->|"no wildcard"| G["DOES NOT EXIST<br>this is a real answer"]
X -->|"wildcard present"| W["cannot conclude<br>NXDOMAIN should be<br>impossible - investigate"]
style E fill:#22c55e,color:#fff
style N fill:#f59e0b,color:#fff
style G fill:#22c55e,color:#fff
style U fill:#ef4444,color:#fff
style W fill:#ef4444,color:#fff🧪 Exercise D2.1 — Answer the real question: does admin-vodafone.seamless.se exist?
# step 1 - is there a wildcard? if there is, nothing below means anything
dig zz9x7q2w-nonexistent-probe.seamless.se +noall +comments
# step 2 - the name itself, with the status visible
dig admin-vodafone.seamless.se +noall +comments +answer
# step 3 - other types, in case step 2 was NODATA
dig admin-vodafone.seamless.se AAAA +short
dig admin-vodafone.seamless.se CNAME +short
# step 4 - confirm against an authoritative server, no cache involved
dig @ns-135.awsdns-16.com admin-vodafone.seamless.se +noall +comments +answer✅ Expected result — click to reveal
$ dig zz9x7q2w-nonexistent-probe.seamless.se +noall +comments
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 44544
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1
$ dig admin-vodafone.seamless.se +noall +comments +answer
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 61804
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
admin-vodafone.seamless.se. 30 IN A 77.83.61.131
$ dig admin-vodafone.seamless.se AAAA +short
(no output)
$ dig admin-vodafone.seamless.se CNAME +short
(no output)
$ dig @ns-135.awsdns-16.com admin-vodafone.seamless.se +noall +comments +answer
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 35503
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
admin-vodafone.seamless.se. 30 IN A 77.83.61.131The answer: yes, it exists, and it is 77.83.61.131. Now the reasoning, because the reasoning is the transferable part.
Step 1 established the result is trustworthy. A random name returned NXDOMAIN, so there is no wildcard in this zone. Had it returned an address, every subsequent check would have been meaningless and you would need a completely different approach.
Step 2 gave NOERROR with ANSWER: 1. Existence confirmed, plus the address.
Step 3 says the name has an A record and nothing else — no IPv6, not an alias. Worth running because a name that returns nothing for A can still exist as a CNAME or with only a TXT, and stopping at step 2 would have you report "does not exist" for a name that does.
Step 4 is the one people skip, and it is the one that makes the answer defensible. Note aa in the flags and the TTL at the full 30 rather than a counted-down value. This came from Route 53 itself, not from a cache. Step 2 tells you what a resolver believes; step 4 tells you what the zone actually says. When those two disagree you have found a stale cache, and that is a completely different investigation.
Two more things this output tells you for free.
The TTL is 30 seconds — even shorter than the apex's 60. Somebody wants to move this address fast. On a customer-named host like this one, that usually means a manually operated failover.
77.83.61.131 is nowhere near the other addresses in this domain — not the AWS Singapore range, not the on-premises ranges. A separate hosting arrangement, which for a customer-specific host is exactly what you would expect and exactly what you should confirm with whoever owns it.
🎯 Interview questions — Proving existence
Q. How do you determine whether a given hostname exists?
I query it and read status: rather than looking for an IP address. NOERROR with a non-zero answer count means it exists with that type; NOERROR with zero answers means the name exists but has no record of that type; NXDOMAIN means no such name; SERVFAIL or REFUSED means I learned nothing and must ask elsewhere.
I check more than one type, because a name can exist as a CNAME or with only a TXT record and still return nothing for A.
The two steps that make the answer defensible, and that most candidates omit. First, probe a random name in the same zone to check for a wildcard — with one present, every name "exists" and the whole exercise is void. Second, confirm against an authoritative server rather than a resolver, so a cached negative answer cannot masquerade as a missing record.
D3 · You cannot list a zone. Here are the six ways people do it anyway
So you reconstruct it: ask HR if you have access (the only complete answer); see who has been issued a visitor badge; guess the common names; and read the names that turn up in other paperwork. Every method except HR gives you a partial list, and you must say so when you hand it over.
Say it once more, because every failed attempt at this comes from forgetting it: the DNS protocol has no operation that returns the contents of a zone. You can ask "is there an A record at this exact name" and nothing else. Enumeration is therefore always indirect — you are reconstructing a list from sources that happen to leak it.
| Method | Completeness | When it works |
|---|---|---|
| 1. The provider's API | Total | Always, if you have credentials. The only method that is actually complete |
| 2. Zone transfer (AXFR) | Total | Almost never permitted. When it is, that is itself the finding |
| 3. Certificate Transparency | Partial | Any name that has ever had a public TLS certificate. Very effective |
| 4. Dictionary probing | Partial | Always available. Finds the conventional names, which is most of them |
| 5. NSEC / NSEC3 walking | Total | Only on DNSSEC-signed zones. Module 07 |
| 6. Your own records leaking | Partial | Always. SPF, MX, NS and reverse DNS all name infrastructure |
🧪 Exercise D3.1 — Try the zone transfer, and understand why it fails
for ns in $(dig +short seamless.se NS); do
echo "--- $ns ---"
dig @"$ns" seamless.se AXFR +time=3 +tries=1 +noall +comments 2>&1 | tail -3
done✅ Expected result — click to reveal
--- ns-1490.awsdns-58.org. ---
;; Connection to 205.251.192.135#53(205.251.192.135) for seamless.se failed: timed out.
;; no servers could be reached
--- ns-722.awsdns-26.net. ---
;; Connection to 205.251.194.210#53(205.251.194.210) for seamless.se failed: timed out.
;; no servers could be reachedAll four time out, and that is the correct, healthy result. Route 53 does not offer AXFR to the public at all.
Read the mechanics, because they matter. AXFR is a TCP query — a whole zone will not fit in a UDP datagram — so dig opens a TCP connection to port 53 and the server declines to complete it. On a BIND server with allow-transfer restricted you would instead get a clean Transfer failed with a REFUSED, which is a more informative rejection.
When this succeeds, stop and write it up. An open AXFR hands a stranger every name in your zone: internal hostnames, staging systems, backup servers, management interfaces, the lot. It is a decades-old audit finding and it still appears, usually on a forgotten secondary rather than the primary — which is why the loop above tries every nameserver rather than just the first.
And this is why the D5 script tries all of them and prints a finding when one answers. You are not expecting success; you are checking that the answer is "no" everywhere.
🧪 Exercise D3.2 — Certificate Transparency: names that a dictionary would never find
# Every publicly trusted certificate is logged, and every certificate
# names the hostnames it covers. That makes CT a searchable index of
# names that have ever been given a certificate.
curl -s 'https://crt.sh/?q=%25.seamless.se&output=json' \
| jq -r '.[].name_value' \
| tr '\n' '\n' | sed 's/^\*\.//' | sort -u✅ Expected result — click to reveal
Run this one yourself — the log is appended to constantly and any output pasted here would be wrong within a week. What matters is how to read it, and that is the same every time.
The output is one hostname per line, deduplicated, with wildcard prefixes stripped. Expect it to be noisy in three specific ways, and expect each kind of noise to be informative:
- Names that no longer resolve. A certificate was issued once and the name has since been retired. These are not junk — they are a history of what the estate used to run, and retired names sometimes come back pointed at somebody else's infrastructure
- Wildcard entries such as *.seamless.se. That tells you a wildcard certificate exists, which is a different thing from a wildcard DNS record, and it means any subdomain can present a valid certificate
- Names created for a single purpose — one-off customer environments, short-lived demos. These are exactly the names a dictionary never finds, and they are the reason to run this at all
The essential step is to feed every name back through dig and keep the ones that still resolve. CT tells you what was certificated; only DNS tells you what is live. The two lists overlap and neither contains the other.
The blind spot to be honest about. CT only sees names that were given a publicly trusted certificate. Anything on an internal CA, anything served over plain HTTP, and anything that is not a web service at all — a database host, an SSH bastion, a mail relay — is invisible here. Those are often precisely the hosts you most wanted to inventory.
If curl and jq are not installed: sudo apt install -y curl jq.
🧪 Exercise D3.3 — Dictionary probing, and reading the result as an architecture
DOMAIN=seamless.se
for n in www mail smtp dev demo jira confluence support crm gw intranet sds api admin vpn; do
ans=$(dig +noall +answer "$n.$DOMAIN" A)
[ -n "$ans" ] && echo "$ans"
done✅ Expected result — click to reveal
www.seamless.se. 3600 IN CNAME seamless.se.
seamless.se. 60 IN A 52.77.52.233
mail.seamless.se. 133 IN CNAME ghs.googlehosted.com.
ghs.googlehosted.com. 69 IN A 192.178.129.121
smtp.seamless.se. 3600 IN CNAME aspmx.l.google.com.
aspmx.l.google.com. 293 IN A 64.233.181.26
dev.seamless.se. 133 IN A 52.76.82.127
demo.seamless.se. 3600 IN A 195.22.81.226
jira.seamless.se. 300 IN A 14.99.30.4
confluence.seamless.se. 300 IN A 14.99.30.4
support.seamless.se. 300 IN A 14.99.30.4
crm.seamless.se. 300 IN A 14.142.42.18
gw.seamless.se. 3600 IN A 195.22.81.194
intranet.seamless.se. 3600 IN CNAME ghs.google.com.
sds.seamless.se. 3600 IN CNAME seamless.se.Twelve names found from fifteen guesses, and api, admin and vpn returned nothing at all. Because D2 already proved there is no wildcard, those three absences are real information, not an inconclusive result.
But the list of names is the boring half. Read the addresses and an architecture appears:
Four distinct hosting environments, visible in the IP ranges alone:
- 52.77.x / 52.76.x — AWS Singapore. The public site and dev
- 14.99.30.4 and 14.142.42.18 — a completely different range hosting Jira, Confluence, support and CRM. On-premises or colocated
- 195.22.81.x — a third range, with demo and gw
- Google — mail, smtp and intranet are all CNAMEs into Google's infrastructure
Now the finding that is worth more than the inventory. jira, confluence and support are all on 14.99.30.4 — one single address. Three business-critical internal services share one host or one ingress point. That is a concentration of risk nobody chose deliberately; it accumulated. You cannot see it by reading a list of names, only by sorting the list by address — which is exactly what D4 does.
And connect it back to D1. The SPF record listed ip4:14.99.30.2 and ip4:14.142.43.230. Those are neighbours of the addresses you just discovered. The organisation's own SPF record told you which IP ranges to look at, before you probed anything. That is method 6 in the table, and it is free.
For real work use a proper tool rather than a shell loop — dnsx, massdns or dnsrecon with a wordlist such as SecLists. They run thousands of names concurrently and handle rate limiting. The loop above is here so you can see exactly what those tools do, which is all they do.
A 485-word sweep run at 40-way concurrency against a public resolver returned 75 "hits". Re-checked one at a time, only 18 were real. zoom, wiki, login, vpn, lan and dozens more had been returning NXDOMAIN all along.
The cause was rate limiting. Public resolvers throttle bursts, and under throttling you get truncated, malformed or empty responses — so a naive script that tests "did dig print anything?" counts those as existence. The false positives clustered alphabetically from the middle of the list onward, which is the tell: the resolver began throttling partway through.
Two defences, and you need both.
- Test status:, never output length. Accept a name only when status: NOERROR and the answer section contains an actual A or CNAME line. [ -n "$output" ] is not an existence test
- Keep concurrency low — around 8 parallel queries with +tries=2, and re-verify every hit serially before it goes into a report
Why this matters more than it sounds. A false positive in a subdomain inventory is worse than a miss: somebody opens a firewall rule, buys a certificate, or writes a monitoring check for a host that has never existed. Always re-verify hits one at a time before the list leaves your hands.
seamless.se returned no DNSKEY record in D1, so the zone is unsigned and this technique is unavailable. Module 07 covers it properly, including why "our zone is signed" and "our zone is enumerable" can be the same sentence.
D4 · From names to serving addresses — and the shared-IP trap
That finding was in the data the whole time and invisible until you changed the sort order.
A list of names is an inventory. A list of names sorted by address is an architecture diagram, and it answers questions the name list cannot: what is co-located, what is single-homed, and what is somewhere you did not expect.
🧪 Exercise D4.1 — Invert the list, then reverse-resolve every address
DOMAIN=seamless.se
NAMES="www mail smtp dev demo jira confluence support crm gw intranet sds"
# names grouped by the address they serve
for n in $NAMES; do
for ip in $(dig +short "$n.$DOMAIN" A | grep -E '^[0-9]+\.'); do
printf '%-16s %s\n' "$ip" "$n.$DOMAIN"
done
done | sort -V
echo "--- reverse DNS of each distinct address ---"
for n in $NAMES; do dig +short "$n.$DOMAIN" A; done \
| grep -E '^[0-9]+\.' | sort -u | while read -r ip; do
printf '%-16s %s\n' "$ip" "$(dig +short -x "$ip" | head -1)"
done✅ Expected result — click to reveal
14.99.30.4 jira.seamless.se
14.99.30.4 confluence.seamless.se
14.99.30.4 support.seamless.se
14.142.42.18 crm.seamless.se
52.76.82.127 dev.seamless.se
52.77.52.233 www.seamless.se
52.77.52.233 sds.seamless.se
195.22.81.194 gw.seamless.se
195.22.81.226 demo.seamless.se
--- reverse DNS of each distinct address ---
14.142.42.18
14.99.30.4
52.76.82.127 ec2-52-76-82-127.ap-southeast-1.compute.amazonaws.com.
52.77.52.233 ec2-52-77-52-233.ap-southeast-1.compute.amazonaws.com.
64.233.181.121 ir-in-f121.1e100.net.
195.22.81.194 gw.seamless.se.
195.22.81.226 The sorted view makes three things visible that the name list hid completely.
1. 14.99.30.4 serves three business-critical services. Jira, Confluence and the support system are one host. One reboot, one certificate, one firewall rule, one failure. This is the finding of the whole exercise and it is invisible until you sort by address.
2. www and sds are the same address — both aliases to the apex. Consistent, and it tells you sds is not a separate system but another name for the main site.
3. Four independent hosting environments. AWS Singapore, two on-premises ranges, and Google. Each one is a different operational contract, a different change process, and a different person to call.
Now the reverse DNS column, which is a health check in its own right:
- gw.seamless.se reverses cleanly to gw.seamless.se. — forward and reverse agree. This is forward-confirmed reverse DNS and it is what a receiving mail server wants to see
- 14.99.30.4, 14.142.42.18 and 195.22.81.226 have no PTR at all. If any of them ever sends mail, it will be penalised or rejected on reputation grounds. Cross-check against the SPF record from D1 — ip4:14.99.30.2 and ip4:14.142.43.230 are in exactly these ranges
- The AWS defaults leak the region — ap-southeast-1 — for free
The one trap that will make you wrong: a shared address does not always mean a shared host. Behind a CDN, a reverse proxy, a Kubernetes ingress or a cloud load balancer, hundreds of unrelated names legitimately share one address, and the co-location you "found" is an artefact. Before concluding anything, check whether the address belongs to a CDN or load balancer — reverse DNS usually tells you, since a CDN PTR is unmistakable. The 14.99.30.4 finding above holds because the reverse is empty and the range is not a CDN's, which is the sort of qualification that turns a guess into a finding.
🎯 Interview questions — Inventory and enumeration
Q. You have been given a domain and asked to find every subdomain. How do you approach it?
I start by saying that DNS has no listing operation, so this is always reconstruction from indirect sources, and the completeness depends entirely on which sources are available to me.
If it is our own domain, I read the zone from the provider's API or console — that is the only complete answer and it ends the exercise. If not: attempt a zone transfer against every nameserver, since one forgotten secondary allowing AXFR gives you the whole zone; query Certificate Transparency logs, which index every name that has ever had a publicly trusted certificate; run a dictionary sweep with a tool like dnsx or massdns; and if the zone is DNSSEC-signed, NSEC walking gives complete enumeration with no guessing.
I would also mine the organisation's own records — SPF ip4: mechanisms, MX targets and NS hostnames all name infrastructure, and reverse DNS across the ranges they reveal finds hosts a dictionary never would.
Two things I would state before starting, and they are what mark out someone who has done this professionally. First, authorisation in writing, since a wide sweep is indistinguishable from reconnaissance in a target's logs. Second, check for a wildcard first — with one present every name resolves, every negative result is meaningless, and the entire exercise has to be approached differently.
Q. Why is an open zone transfer a security finding?
Because AXFR returns the complete contents of the zone to whoever asks. That is every internal hostname, every staging and test system, every management interface, every backup host — a free map of the estate, and a target list.
It is not a vulnerability in DNS; it is a configuration that was appropriate for a secondary and got left open to the world. allow-transfer restricted to the secondaries' addresses, ideally with TSIG authentication, is the fix.
The detail that shows field experience: when it appears, it is almost never on the primary. It is on a secondary that someone stood up years ago and nobody has thought about since — which is why you test every nameserver in the NS set, not just the first one your tool picks.
D5 · The reusable audit script
A new name nobody mentioned, a service that quietly moved to a different data centre, a name plate that disappeared — all of those show up in the difference, not in the snapshot.
Everything in Part D, in one file. Read-only, no dependencies beyond dig, and safe to run against your own estate on a schedule.
#!/usr/bin/env bash
# dns-audit.sh <domain> [wordlist]
# Read-only DNS inventory. Run only against domains you own or are
# authorised to assess.
set -uo pipefail
DOMAIN="${1:?usage: dns-audit.sh <domain> [wordlist]}"
WORDLIST="${2:-}"
hr() { printf '\n== %s ==\n' "$1"; }
hr "ZONE RECORDS: $DOMAIN"
for t in SOA NS A AAAA MX TXT CAA DNSKEY; do
out=$(dig +noall +answer "$DOMAIN" "$t")
[ -n "$out" ] && echo "$out" || printf '%-8s (none)\n' "$t"
done
hr "AUTHORITATIVE SERVERS"
dig +short "$DOMAIN" NS | sort
hr "WILDCARD CHECK"
probe="zz$RANDOM$RANDOM-wildcard-probe.$DOMAIN"
st=$(dig "$probe" +noall +comments | grep -o 'status: [A-Z]*' | head -1)
if [ "$st" = "status: NXDOMAIN" ]; then
echo "no wildcard ($st) -> existence checks are meaningful"
else
echo "WILDCARD PRESENT ($st) -> every name resolves; enumeration unreliable"
fi
hr "ZONE TRANSFER (AXFR) ATTEMPT"
for ns in $(dig +short "$DOMAIN" NS); do
if dig "@$ns" "$DOMAIN" AXFR +time=3 +tries=1 2>/dev/null | grep -q "^$DOMAIN"; then
echo "OPEN AXFR on $ns <-- FINDING, report it"
else
echo "refused/timeout on $ns (expected)"
fi
done
[ -z "$WORDLIST" ] && { hr "DONE"; exit 0; }
hr "NAME -> ADDRESS INVENTORY"
printf '%-34s %-8s %s\n' "NAME" "STATUS" "RESOLVES TO"
while read -r w; do
[ -z "$w" ] && continue
n="$w.$DOMAIN"
ans=$(dig +noall +answer "$n" A)
[ -z "$ans" ] && continue
ips=$(echo "$ans" | awk '$4=="A"{print $5}' | paste -sd, -)
cn=$(echo "$ans" | awk '$4=="CNAME"{print $5}' | head -1)
if [ -n "$cn" ]; then
printf '%-34s %-8s CNAME %s -> %s\n' "$n" "ALIAS" "$cn" "${ips:--}"
else
printf '%-34s %-8s %s\n' "$n" "A" "$ips"
fi
done < "$WORDLIST"
hr "REVERSE DNS OF DISCOVERED ADDRESSES"
while read -r w; do
[ -z "$w" ] && continue
dig +short "$w.$DOMAIN" A
done < "$WORDLIST" | grep -E '^[0-9]+\.' | sort -u | while read -r ip; do
printf '%-16s %s\n' "$ip" "$(dig +short -x "$ip" | head -1)"
done🧪 Exercise D5.1 — Run the whole audit end to end
chmod +x dns-audit.sh
printf '%s\n' www mail smtp dev demo jira confluence support crm gw intranet sds api admin vpn > words.txt
./dns-audit.sh seamless.se words.txt✅ Expected result — click to reveal
== WILDCARD CHECK ==
no wildcard (status: NXDOMAIN) -> existence checks are meaningful
== ZONE TRANSFER (AXFR) ATTEMPT ==
refused/timeout on ns-1490.awsdns-58.org. (expected)
refused/timeout on ns-722.awsdns-26.net. (expected)
refused/timeout on ns-135.awsdns-16.com. (expected)
refused/timeout on ns-1760.awsdns-28.co.uk. (expected)
== NAME -> ADDRESS INVENTORY ==
NAME STATUS RESOLVES TO
www.seamless.se ALIAS CNAME seamless.se. -> 52.77.52.233
mail.seamless.se ALIAS CNAME ghs.googlehosted.com. -> 192.178.209.121
smtp.seamless.se ALIAS CNAME aspmx.l.google.com. -> 192.178.209.27
dev.seamless.se A 52.76.82.127
demo.seamless.se A 195.22.81.226
jira.seamless.se A 14.99.30.4
confluence.seamless.se A 14.99.30.4
support.seamless.se A 14.99.30.4
crm.seamless.se A 14.142.42.18
gw.seamless.se A 195.22.81.194
intranet.seamless.se ALIAS CNAME ghs.google.com. -> 142.251.183.121
sds.seamless.se ALIAS CNAME seamless.se. -> 52.77.52.233
== REVERSE DNS OF DISCOVERED ADDRESSES ==
14.142.42.18
14.99.30.4
195.22.81.194 gw.seamless.se.
195.22.81.226
52.76.82.127 ec2-52-76-82-127.ap-southeast-1.compute.amazonaws.com.
52.77.52.233 ec2-52-77-52-233.ap-southeast-1.compute.amazonaws.com.The Google addresses will differ every run — those CNAMEs resolve into a large rotating pool, which is itself worth noticing.
Four design decisions in this script are worth copying into anything you write:
1. The wildcard check runs before the enumeration and is printed prominently. If a wildcard exists, everything below it is noise, and the reader needs to know that before they read it.
2. The AXFR loop covers every nameserver. As D3 explained, the open one is usually the forgotten secondary, so checking only the first is checking the wrong one.
3. CNAMEs are labelled ALIAS and the target is kept. A flattened +short output would show only the final address and hide the fact that a third party sits in the path. Knowing that mail points into Google's estate is the useful part.
4. The reverse lookup is a separate final section. It is the part that tells you about hosting, region and mail reputation, and it is the part people leave out.
Where to take it next. Emit CSV and commit it to Git, and every run becomes a diff. A new name appearing, an address moving between hosting environments, or a PTR disappearing all become visible in a code review rather than in an incident. That is the point of the whole exercise: not the snapshot, but noticing the change.
D6 · What these names are called, and how deep they go
Adding a room is still your house — you can repaint it this afternoon without asking anyone. That is a subdomain: just another record in your own zone.
Renting a floor to another company means they control it now. Same building, different keys, and any change goes through them. That is a delegated zone — and one command tells you which of the two you are looking at.
Module 01 A2 established that a name is a path of labels. Here is the vocabulary that goes with it, because the words get used loosely and the looseness is where the confusion starts.
| Name | What it is |
|---|---|
| se. | The TLD. One label under the root |
| seamless.se. | The registrable domain, and here also the zone apex — the name the zone is named after |
| admin-vfo.seamless.se. | A subdomain of seamless.se. Third level. Also just "a domain name" — there is no separate word for it |
| www.admin-vfo.seamless.se. | A subdomain of admin-vfo.seamless.se, which is itself a subdomain of seamless.se. Fourth level |
So there is no special name for admin-vfo.seamless.se. It is a domain name, and it is a subdomain of seamless.se. If you need to be precise about depth, say third-level or fourth-level.
Adding admin-vfo.seamless.se to your DNS does not create a new zone. It creates a record inside the existing seamless.se zone — same file, same nameservers, same SOA, same administrator.
A zone is only created when the parent formally hands authority over with NS records — a delegation. That is a deliberate act, usually because a different team or company will administer that branch.
And you can tell which you are looking at with one query, which is the exercise below.
🧪 Exercise D6.1 — Is this a subdomain, or its own zone?
# a name that exists three levels deep
dig +short admin-vfo.seamless.se A
# THE test: ask which zone is authoritative for it.
# Any query that returns an SOA tells you which zone owns the name.
dig +noall +authority admin-vfo.seamless.se AAAA
# and check directly for a delegation
dig +noall +answer admin-vfo.seamless.se NS✅ Expected result — click to reveal
$ dig +short admin-vfo.seamless.se A
185.64.27.150
$ dig +noall +authority admin-vfo.seamless.se AAAA
seamless.se. 900 IN SOA ns-1490.awsdns-58.org. awsdns-hostmaster.amazon.com. 1 7200 900 1209600 86400
$ dig +noall +answer admin-vfo.seamless.se NS
(no output)The second command is the whole exercise, and the answer is in the owner-name column. The SOA that came back belongs to seamless.se, not to admin-vfo.seamless.se.
That single fact tells you admin-vfo.seamless.se is a plain record inside the seamless.se zone. No separate zone, no separate nameservers, no separate administrator. It is a line in the same file as www and jira, and it is changed in the same Route 53 hosted zone.
The third command confirms it from the other direction — no NS records at that name, so nothing has been delegated.
What it would look like if it were a zone. The SOA in the AUTHORITY section would read admin-vfo.seamless.se. rather than seamless.se., and dig NS admin-vfo.seamless.se would return nameservers. Then it would be a genuinely separate administrative unit — possibly run by another team, possibly by the customer themselves.
Why you care in daily work. It answers "who do I ask to change this record". A subdomain-that-is-a-record is yours to change in your own hosted zone. A subdomain-that-is-a-zone belongs to whoever holds those nameservers, and your change has to go through them. Getting this backwards costs a day of chasing the wrong team. Module 03 covers how the delegation itself works.
admin-vfo.seamless.se exists. www.admin-vfo.seamless.se returns NXDOMAIN — it does not exist at all.
This is Module 01 A2.1 restated one level deeper: nothing in DNS makes www.X follow from X. Somebody has to create it, one name at a time. www.seamless.se exists only because a CNAME was deliberately added for it; nobody added the equivalent for admin-vfo, so that name simply is not there.
The practical habit: when a URL fails, dig the exact hostname from the address bar — including the www. — before you look at anything else. Half the time the hostname was never created.
🧪 Exercise D6.2 — The gap in the D5 script: it only looks one level deep
# D3 and D5 probe <word>.seamless.se - a single label.
# Deeper names need a nested sweep.
L1="admin-vfo admin-vodafone dev demo jira"
L2="www admin api portal test mail vpn"
printf '%-40s %-10s %s\n' "NAME" "STATUS" "ADDRESS"
for a in $L1; do
st=$(dig "$a.seamless.se" +noall +comments | grep -o 'status: [A-Z]*' | head -1 | cut -d' ' -f2)
printf '%-40s %-10s %s\n' "$a.seamless.se" "$st" "$(dig +short $a.seamless.se A | tr '\n' ' ')"
for b in $L2; do
st2=$(dig "$b.$a.seamless.se" +noall +comments | grep -o 'status: [A-Z]*' | head -1 | cut -d' ' -f2)
[ "$st2" = "NOERROR" ] && printf ' %-38s %-10s %s\n' "$b.$a.seamless.se" "$st2" "$(dig +short $b.$a.seamless.se A | tr '\n' ' ')"
done
done✅ Expected result — click to reveal
NAME STATUS ADDRESS
admin-vfo.seamless.se NOERROR 185.64.27.150
admin-vodafone.seamless.se NOERROR 77.83.61.131
dev.seamless.se NOERROR 52.76.82.127
demo.seamless.se NOERROR 195.22.81.226
jira.seamless.se NOERROR 14.99.30.4Thirty-five fourth-level probes and not one of them exists. The indented lines never printed, because every www.*, admin.* and api.* under those five names returned NXDOMAIN.
That is a real and useful finding: this estate is flat. Everything lives one label under seamless.se. There is no www.admin-vfo, no api.dev, no nested structure at all. Knowing the shape is flat means the one-level sweep in D5 is sufficient here, and you can stop looking.
Also note admin-vfo and admin-vodafone are different names on different addresses — 185.64.27.150 and 77.83.61.131. Similar naming, separate environments. Do not assume two names that look related point anywhere near each other; the address column is the only thing that tells you.
Why brute force gets expensive fast, and why you should not lead with it. One level with a 5,000-word list is 5,000 queries. Two levels is 5,000 × 5,000 — twenty-five million, which is neither polite nor practical. So the realistic method is: sweep level one, then sweep level two only beneath the names that actually exist, which is what the nested loop above does.
Which is exactly why Certificate Transparency (D3.2) matters more at depth than at level one. CT gives you the full hostname of anything that ever had a public certificate, however deep it sits, with no guessing and no query volume. For a nested estate, run CT first and use the dictionary only to fill gaps.
And the reminder from D2: all of this depends on there being no wildcard. Note too that a wildcard at *.seamless.se would not match www.admin-vfo.seamless.se — wildcards cover one label only, so a fourth-level name is unaffected by a third-level wildcard. That asymmetry surprises people during audits.
🎯 Interview questions — Subdomains and zones
Q. What is the difference between a subdomain and a zone?
A subdomain is a naming relationship — any name below another in the tree. A zone is an administrative unit: a set of records served together by one set of nameservers, with one SOA.
Creating admin.example.com normally just adds a record to the example.com zone. It becomes a separate zone only when the parent publishes NS records for it — a delegation — which is a deliberate handover of authority, usually to a different team or organisation.
The one-command test, which is what makes this a practical answer rather than a definition: query any name under it and read the owner name of the SOA in the AUTHORITY section. If the SOA says example.com, the name is a record in the parent zone. If it says admin.example.com, it is its own zone. That tells you immediately whether the change is yours to make or somebody else's.
Q. example.com works but www.example.com does not. What happened?
They are two different names in the tree and nothing in DNS links them. www has to be created explicitly, usually as a CNAME to the apex or as its own address record, and in this case nobody did — or it was deleted.
I would confirm with dig www.example.com +noall +comments and expect NXDOMAIN, then check the authoritative server directly to rule out a cached negative.
The reason this is worth getting right instantly: the www convention is so universal that people assume it is a protocol behaviour. It is not — it is a record someone has to maintain, and "we migrated DNS and forgot the www CNAME" is a genuinely common outage. The same reasoning applies at any depth: www.admin.example.com does not follow from admin.example.com either.
Part E · Putting it together
E1 · How this all fits — the complete picture
Diagram source
flowchart TD
subgraph ZONE["📗 THE ZONE - one file, one administrative unit"]
SOA["SOA - exactly one, at the apex<br>serial, timers, negative TTL"]
NS["NS - who is authoritative"]
APEX["apex records<br>A, MX, TXT/SPF, CAA<br>NO CNAME allowed here"]
SUB["names below the apex<br>A · AAAA · CNAME · SRV · TXT"]
WILD["* wildcard<br>fires only when a name<br>does not otherwise exist"]
end
ZONE --> RRSET["every name+type is an RRSET<br>shared TTL · unordered<br>replaced as a whole"]
RRSET --> Q["a query names ONE name<br>and ONE type"]
Q --> R{"what comes back"}
R -->|"records"| OK["NOERROR + data"]
R -->|"name exists,<br>wrong type"| ND["NOERROR + ANSWER 0"]
R -->|"no such name"| NX["NXDOMAIN<br>SOA carries the negative TTL"]
ENUM["there is NO list operation<br>-> provider API · AXFR · CT logs<br>dictionary · NSEC · your own records"]
Q -.->|"which is why"| ENUM
style SOA fill:#8b5cf6,color:#fff
style WILD fill:#ef4444,color:#fff
style RRSET fill:#f59e0b,color:#fff
style ENUM fill:#22c55e,color:#fffFour ideas from this module, and everything else follows from them.
- The RRset is the unit. Shared TTL, no order, replaced whole. This explains DNS APIs, DNSSEC signing, and the automation bug that deletes five records while adding one.
- A CNAME captures the whole name, not one record type. Every CNAME rule — no coexisting records, illegal at the apex, breaks mail — is that one sentence applied.
- OK from named-checkzone means "this parses", not "this is correct". The expensive mistakes all load cleanly.
- There is no list operation. Enumeration is always reconstruction, its completeness depends on the source, and a wildcard destroys it entirely.
E2 · Production practice
| Habit | Why |
|---|---|
| Treat every DNS API change as a whole-RRset replacement | Sending one A record to an API that takes the set deletes the other five, returns success, and looks like it worked |
| Run named-checkzone -D and read the dump, not just the OK | The trailing-dot bug, @ expansion and inherited TTLs are only visible in the canonical form |
| Never write a half-qualified name in a zone file | www or www.example.com. — the middle form silently becomes www.example.com.example.com. |
| Check for a wildcard before trusting any existence result | One query decides whether every NXDOMAIN in your investigation means anything at all |
| Verify record changes against an authoritative server, not a resolver | aa set and a full TTL is ground truth; anything else may be a cache showing you the past |
| Follow MX and CNAME targets one hop further | An MX pointing at a hostname with no address, or at a CNAME, is a silent partial mail outage |
| Publish a null MX on every domain that receives no mail | Otherwise senders fall back to the A record and retry for days, generating tickets |
| Publish CAA naming every CA you actually use, plus iodef | With no CAA, every trusted CA on earth may issue for you. With too narrow a CAA, renewal fails at 3am |
| Read pct= whenever you read p= in a DMARC record | p=reject; pct=25 is a rollout in progress that reads as "protected" to anyone skimming |
| Count your SPF include: mechanisms against the 10-lookup limit | A vendor expanding their record pushes you to permerror and bounces your mail with no change on your side |
| Test AXFR against every nameserver, not just the first | The open one is nearly always a forgotten secondary, not the primary |
| Sort your inventory by address, not by name | Co-location and single points of failure are invisible in a name list and obvious in an address list |
| Commit the audit output to Git and review the diff | The snapshot is mildly interesting; the change between snapshots is the finding |
E3 · Capstone exercise
Part 1 — Build. Write a zone file for corp.internal that satisfies all of the following, and prove each with named-checkzone:
- Apex serves a web address, and www reaches the same place — without breaking mail
- api is served by three addresses that must fail over together
- Mail is handled by two servers with a clear primary and fallback, neither target an alias
- A subdomain that accepts no mail at all is declared as such, machine-readably
- Only one named CA may issue certificates, and violations are reported somewhere
- A negative answer for this zone may be cached for no more than 5 minutes
- The zone must load with zero warnings under named-checkzone
Part 2 — Read. For any domain you are authorised to assess, produce a one-page report answering:
- Who operates the DNS, who operates mail, and how you know
- Whether the zone has a wildcard, and what that means for the rest of your report
- Every name you can find, with its serving address, sorted by address
- The single largest concentration of risk visible in the address list
- Three findings that are about what is missing rather than what is present
✅ Model answer — attempt it first, then click
Part 1 — the zone.
$TTL 3600
$ORIGIN corp.internal.
@ IN SOA ns1.corp.internal. hostmaster.corp.internal. (
2026081701 ; serial
7200 ; refresh
900 ; retry
1209600 ; expire
300 ) ; requirement 6 - negative TTL 5 min
@ IN NS ns1.corp.internal.
@ IN NS ns2.corp.internal.
ns1 IN A 192.0.2.10
ns2 IN A 192.0.2.11
; requirement 1 - apex CANNOT be a CNAME, so a real A record,
; and www is an alias to the apex. The alias is at www, not at @.
@ IN A 192.0.2.20
www IN CNAME @
; requirement 2 - one RRset, three members, one shared TTL
api IN A 192.0.2.30
api IN A 192.0.2.31
api IN A 192.0.2.32
; requirement 3 - targets are real hostnames with A records, not aliases
@ IN MX 10 mail1.corp.internal.
@ IN MX 20 mail2.corp.internal.
mail1 IN A 192.0.2.40
mail2 IN A 192.0.2.41
; requirement 4 - null MX, RFC 7505
static IN A 192.0.2.50
static IN MX 0 .
; requirement 5 - single issuer plus violation reporting
@ IN CAA 0 issue "letsencrypt.org"
@ IN CAA 0 iodef "mailto:[email protected]"named-checkzone corp.internal db.corp.internal # requirement 7
named-checkzone -D corp.internal db.corp.internal # and READ itThe seven traps, and where each requirement tries to catch you:
- Requirement 1 is the apex-CNAME trap. A CNAME at @ collides with SOA, NS, MX and CAA — CNAME and other data, zone rejected. The alias belongs at www
- Requirement 2's phrase "fail over together" is the RRset property: one TTL, shared, not chosen per record. Writing three different TTLs gets you TTL set to prior TTL and two silently discarded values
- Requirement 3 says "neither target an alias" — an MX pointing at a CNAME is illegal per RFC 2181 and named-checkzone warns while still returning OK, which is the trap
- Requirement 4 is MX 0 . — a single MX whose target is the root. A missing MX is not the same thing: senders then fall back to the A record
- Requirement 5 needs two records, not one. issue alone silently discards violation reports; iodef is what makes them reach you
- Requirement 6 is the SOA MINIMUM field, and the subtlety is that the effective negative TTL is min(MINIMUM, SOA TTL). With $TTL 3600 and MINIMUM 300, you get 300 — correct. Had you set MINIMUM to 7200 you would have got 3600, not 7200
- Requirement 7 means reading the output. OK alone does not satisfy it, because OK appears alongside warnings
Part 2 — the report, using seamless.se as the worked example.
8. Operators. DNS is AWS Route 53 — the four awsdns nameservers, spread across .com, .net, .org and .co.uk so no single TLD is a shared failure. Mail is Google Workspace — aspmx.l.google.com at preference 10 with a 20/30 fallback ladder. Both conclusions come from record values, not from asking anyone.
9. Wildcard. None. A random name returns NXDOMAIN, so every negative result in this report is real information rather than an artefact. This has to be stated first, because without it nothing below can be relied on.
10 and 11. Inventory sorted by address:
14.99.30.4 jira · confluence · support <-- concentration
14.142.42.18 crm
52.76.82.127 dev AWS ap-southeast-1
52.77.52.233 www · sds (apex) AWS ap-southeast-1
195.22.81.194 gw PTR agrees - FCrDNS ok
195.22.81.226 demoThe largest concentration of risk is 14.99.30.4: Jira, Confluence and the support system on one address. One host, one certificate, one firewall rule, one reboot. It is invisible in a list of names and unmissable once sorted by address — which is the entire reason to sort by address.
12. Three findings about absences — the ones a "what exists" report never produces:
- No AAAA anywhere. IPv6-only clients cannot reach this estate without translation, and IPv6-only mobile networks are increasingly common
- No CAA record. Every publicly trusted CA in the world is currently authorised to issue certificates for this domain. One record fixes it
- No PTR for 14.99.30.4, 14.142.42.18 or 195.22.81.226 — and the SPF record authorises sending from those very ranges. Any mail from them is exposed to a reverse-DNS deliverability penalty
The five things most people miss on this capstone:
- Putting the wildcard check first in Part 2. It is a precondition for the report's validity, not a bullet point inside it
- iodef as well as issue. Almost everyone writes the issue record alone
- Reporting absences at all. Requirement 12 exists because "what is missing" is where the security findings live, and a tool-generated inventory never surfaces them
- Sorting by address. The concentration finding is unreachable any other way
- Not confusing shared address with shared host. Behind a CDN or an ingress, co-location is an artefact — the finding above holds only because the reverse DNS is empty and the range is not a CDN's, and saying so is what makes it a finding rather than a guess
E4 · Official documentation
| Link | Covers |
|---|---|
| RFC 1035 §3.2–3.5 and §5 | Every original record type, and the master file format from Part C |
| RFC 2181 — Clarifications to the DNS Specification | §5 RRsets and the shared-TTL rule · §10.1 CNAME restrictions · §10.3 MX and NS targets. The most operationally useful RFC in DNS |
| RFC 1912 — Common DNS Operational and Configuration Errors | Effectively a checklist of every mistake in Part C. Short, and worth reading end to end |
| RFC 2308 — Negative Caching | The SOA MINIMUM field, and why the negative TTL is the lesser of two numbers |
| RFC 4592 — The Role of Wildcards | All four wildcard properties from C4, stated formally |
| RFC 3596 — AAAA · RFC 2782 — SRV · RFC 8659 — CAA | The three modern types, one RFC each |
| RFC 7208 — SPF · RFC 6376 — DKIM · RFC 7489 — DMARC · RFC 7505 — null MX | Everything mail-related that lives in DNS |
| RFC 8482 — minimal responses to ANY · RFC 5936 — AXFR | Why ANY does not list a zone, and what a real zone transfer is |
| BIND 9 — Configurations and Zone Files · named-checkzone | The zone file format as implemented, and the validator from C2 |
| Route 53 — Supported DNS record types · alias vs non-alias | The apex-CNAME workaround from C3, on the provider this module's examples use |
| IANA DNS Parameters | The live registry of every record type. Always current, unlike any RFC |
Read RFC 1912 first and read all of it. It is short, it is entirely operational, and it is the only RFC here that reads like a checklist written by someone who has been paged.
Use RFC 2181 as a reference, not a read. Go to §5, §10.1 and §10.3 when you need to win an argument about RRset TTLs, CNAME coexistence or MX targets. Those three sections settle most real disputes.
For record types, go to IANA before any RFC. New types are still being added; the registry is current and links to the defining RFC for each.
The offline route. man named-checkzone and man dig cover the tooling. named-checkzone -D is itself documentation: it shows you what the server believes your file says, which is more useful than any specification when you are debugging one specific zone.
E5 · Self-assessment
Answer each out loud before opening it.
1. What is an RRset, and name two behaviours that follow from it.
Every record sharing an owner name, class and type — treated as one indivisible object.
Two consequences: all members must share a TTL, because they are cached and expire as one; and you replace the whole set, never one member, which is why DNS APIs are replace-oriented and why a script that sends a single value deletes the rest.
A third, if you want it: DNSSEC signs RRsets, not records.
2. Explain in one sentence why a CNAME cannot coexist with any other record.
Because a CNAME redirects the name, not a record type — once the name is an alias it is a signpost rather than a destination, so there is nowhere for other records to live.
Everything else follows: it cannot sit at the apex, because the apex must hold SOA and NS; and adding one to a name with MX records silently destroys mail delivery.
3. Name the seven SOA fields, and say which one you would change to make deleted records disappear faster.
MNAME, RNAME, SERIAL, REFRESH, RETRY, EXPIRE, MINIMUM.
MINIMUM — it is the negative caching TTL, so it governs how long "this does not exist" is remembered.
The precision that matters: the effective negative TTL is the lesser of MINIMUM and the SOA record's own TTL, so lowering MINIMUM alone does nothing if the SOA's TTL is already the smaller number.
4. Why does dig ANY example.com not give you every record?
Because ANY never meant that. It returns whatever the answering server happens to hold — cached fragments from a resolver, and never a guaranteed inventory from an authoritative server.
Since RFC 8482 most large operators return a minimal synthetic response instead, often a HINFO record reading RFC8482, because ANY was the engine of DNS amplification attacks.
The real answer is that DNS has no listing operation at all, so you loop over types.
5. What are the four properties of a wildcard record?
It fires only when the queried name does not otherwise exist in any form; it matches exactly one label; it is type-specific; and the response carries the queried name, so nothing reveals that a wildcard produced it.
The consequence that matters operationally: a wildcard abolishes NXDOMAIN for the whole zone, which destroys existence checks, monitoring that relies on a name failing, and every enumeration technique.
6. named-checkzone prints OK. What have you actually proved?
That the file parses. Nothing more.
The trailing-dot bug, an MX pointing at a CNAME, and mismatched TTLs in an RRset all produce OK — two with a warning, one with complete silence.
The verification that counts is named-checkzone -D — read the canonical dump — and then querying the loaded zone.
7. Why can't you put a CNAME at the apex, and what does your provider offer instead?
The apex must carry SOA and NS, and a CNAME excludes all other types at the same name. It is a structural contradiction.
Providers offer alias-style pseudo-records — Route 53 alias, Cloudflare CNAME flattening, ALIAS / ANAME elsewhere. The authoritative server resolves the target internally and emits a real A or AAAA, so nothing non-standard goes on the wire.
Two catches: the flattening happens from the provider's network position, which can hand your users a badly chosen address for a geo-distributed target; and the records are provider-specific, so a DNS migration means re-creating each one by hand.
8. Someone asks you to list every subdomain of a domain. What is the first thing you say?
That DNS has no listing operation, so this is reconstruction from indirect sources and its completeness depends entirely on which sources I can reach.
If it is our own domain, reading the zone from the provider's API is the only complete method and ends the exercise.
Otherwise: AXFR against every nameserver, Certificate Transparency logs, dictionary probing, NSEC walking if the zone is signed, and mining our own SPF, MX, NS and reverse DNS for infrastructure.
And before any of that: authorisation in writing, and a wildcard check — with a wildcard present, every result is meaningless.
9. dig +short name.example.com prints nothing. List everything that could mean.
NXDOMAIN — no such name. NODATA — the name exists but has no A record, perhaps only a CNAME, TXT or AAAA. SERVFAIL — the resolver could not complete. REFUSED — policy. Or no response at all — a timeout.
+short collapses all five into one empty line, which is why it must never appear in a check.
The correct procedure is +noall +comments to read status:, then other record types, then a wildcard probe, then confirmation against an authoritative server.
10. You find three business services on one IP address. Is that a finding?
Only after one check. Behind a CDN, reverse proxy, Kubernetes ingress or cloud load balancer, hundreds of unrelated names legitimately share an address and the co-location is an artefact.
I would check the reverse DNS and the address's owner. If the PTR is a CDN's, it is noise. If the PTR is empty and the range belongs to the organisation, it is a genuine single point of failure worth raising.
Stating that qualification is what turns a guess into a finding — and it is the difference between a report people act on and one they stop reading.
11. A domain has p=reject in DMARC and no CAA record. Summarise its posture.
Mixed, and both halves need a caveat.
On mail: p=reject is the strongest DMARC policy, but I would read pct= alongside it — p=reject; pct=25 is a rollout enforcing on a quarter of failing mail, which reads as "protected" to anyone skimming.
On certificates: no CAA means every publicly trusted CA in the world may issue for this domain. It is not a blocked state, it is a wide-open default, and it is one of the cheapest findings to remediate — one record, no traffic impact.
You now know what an NS record is and that example.com is delegated by com. Module 03 follows a resolver as it walks that path for real: root hints, referrals, glue records and why they must exist, dig +trace line by line, how the cache is populated at every step, and what "DNS propagation" is actually describing. It also explains the AUTHORITY section you saw filled with 13 records in Module 01 C4.1 and never had explained.
📚 Sources for the interview questions
All record data in this module was captured live against seamless.se, example.com, google.com and jabber.org on 17 August 2026 using dig 9.18, and every zone-file error in Part C was reproduced with named-checkzone 9.18. Addresses and TTLs will have moved on by the time you read it; the shapes will not.
Specifications verified directly: RFC 1035, RFC 1912, RFC 2181, RFC 2308, RFC 2782, RFC 3596, RFC 4592, RFC 5936, RFC 7208, RFC 7489, RFC 7505, RFC 8482, RFC 8659, RFC 9162, and the IANA DNS Parameters registry.
Question selection cross-referenced against publicly published 2026 DNS and networking interview question sets:
- Top 25 DNS Interview Questions and Answers for 2026 — nitizsharma.com — record types, zone files, SOA serial, wildcard records, reverse DNS
- Top 30 Most Common DNS Interview Questions — Verve AI — SRV records, reverse DNS and mail, checking records for a domain
- Interview Questions & Answers for DNS — DevOpsSchool — A, AAAA, PTR, MX, zone types
- DNS Interview Questions & Answers for Network Engineers & Software Developers — Level Up Coding
- 75+ Network Engineer Interview Questions for 2026 — Taggd
Answers were rewritten and deepened rather than reproduced. The published sets ask "what is an MX record" and answer in one line; the operational half — the 10-lookup SPF limit, pct= in DMARC, MX targets that must not be aliases, the null MX — is what actually gets discussed in the room.