Homelab · Part 5

Homelab Build — Part 5 — DNS

With dc01 and dc02 replicating, this part turns their inherited, unconfigured DNS settings into a deliberate setup: an explicit forwarder instead of whatever pfSense handed them, and aging and scavenging turned on before rebuilt lab VMs start leaving stale records behind.

Quick idea: Part 4 left DNS running on whatever forwarders dc01 inherited during promotion. This part sets an explicit forwarder with Set-DnsServerForwarder, then enables aging on the zone and scavenging on one designated domain controller so records left behind by rebuilt lab VMs actually get cleaned up.
Forwarder

Where a DNS server sends queries it can’t answer from its own zones or cache.

Zone Aging

Timestamps records so the server can tell a stale one from one still in use.

Scavenging

The periodic job that actually deletes records once they’ve aged past both intervals.

What This Part Covers

Part 4 promoted dc01 and dc02 and confirmed they replicate, but it deliberately left DNS in whatever state Install-ADDSForest -InstallDns put it in. That’s fine for standing up a forest — it is not fine to leave permanently. This part makes two things explicit: which server dc01 and dc02 forward unresolved queries to, and whether stale records ever get removed.

Think of the forwarder setting as the lab’s only deliberate opinion about where “the rest of the internet” lives. Everything internal already resolves through AD-integrated DNS; the forwarder is the one line that decides what happens when a query is for something outside lab.example.com entirely.

Series scope: This post continues from Part 3 — Networking with pfSense and Part 4 — Domain Controllers. It assumes dc01 (10.10.10.10) and dc02 (10.10.10.11) are both promoted, replicating, and each already pointing its DNS client at itself first and the other second. For a full walkthrough of what aging and scavenging actually do and how the intervals interact, see How to Safely Enable DNS Scavenging and Windows DNS Server — this post applies that background to this specific lab rather than repeating it.

Why This Needs a Deliberate Forwarder, Not the Inherited One

When Install-ADDSForest -InstallDns ran on dc01 in Part 4, the new DNS Server service typically carried over whatever forwarder dc01’s own network settings pointed at right before promotion — in this lab, that was pfSense’s Servers-segment interface, 10.10.10.1, which Part 4 had configured as dc01’s temporary DNS client server. That happens to be a reasonable forwarder for this lab, but “happens to be reasonable” and “was deliberately chosen and verified” are different things, and only one of them belongs in a build worth trusting.

Inherited Setting

Works today because of what dc01’s network settings happened to be during promotion. Nothing confirms it stays correct.

Explicit Setting

Set once with Set-DnsServerForwarder, verified with Get-DnsServerForwarder, and documented here.

Root Hints

The fallback path if the forwarder doesn’t answer — left enabled rather than disabled, covered below.

pfSense’s own resolver ultimately reaches the internet through the lab’s WAN connection, so forwarding to 10.10.10.1 keeps external DNS traffic consolidated at the same box already doing NAT and firewalling for the lab — one place to see what’s leaving the network, instead of two domain controllers each independently querying root servers.

Forwarders, Conditional Forwarders, and Root Hints

A DNS server configured with a forwarder tries its own zones and cache first. Only if that fails does it forward the query — as a recursive query — to the configured forwarder IP addresses, in the order they’re listed. If none of them answer, and root hints are still enabled, the server falls back to iterative queries against the internet root servers directly.

A conditional forwarder is the same idea scoped to one specific domain name rather than everything: queries for names ending in a particular suffix go to a specific set of servers, while everything else follows the normal forwarder or root hints path. This lab has no other DNS namespace to conditionally forward to yet — that becomes relevant if a future part adds a second forest or a partner domain, not before.

Key point: Root servers can’t be configured with a standard forwarder — querying one either returns a referral to another server or an NXDOMAIN failure. Root hints exist purely so a DNS server can find those root servers itself; they are not an alternate forwarder list.

Configuring the Forwarder

Set the forwarder explicitly on both dc01 and dc02 rather than trusting whichever one inherited a working setting during promotion. Set-DnsServerForwarder overwrites the existing forwarder list, so this is safe to run even if the current value already happens to be correct.

# On dc01 and dc02: set pfSense as the explicit forwarder, keep root hints enabled as a fallback
Set-DnsServerForwarder -IPAddress 10.10.10.1 -UseRootHint $true -PassThru

# Confirm the forwarder list and whether root hint fallback is enabled
Get-DnsServerForwarder
Practical rule: Leave -UseRootHint $true unless there’s a specific reason external resolution should hard-fail when pfSense is unreachable. In this lab, pfSense going down already breaks the WAN path for everything else, so root hints as a fallback cost nothing and occasionally save a diagnosis.

Checking the Zone’s Replication Scope

Install-ADDSForest -InstallDns created lab.example.com as an Active Directory-integrated zone stored in the domain-wide DomainDnsZones application partition by default — meaning it replicates to every DNS server running on a domain controller in lab.example.com, which today is both dc01 and dc02. That is the correct scope for a single-domain forest like this one; ForestDnsZones scope only matters once a second domain joins the forest and needs to see the same zone.

# Confirm the zone is AD-integrated and check its current replication scope
Get-DnsServerZone -Name "lab.example.com" | Select-Object ZoneName, ZoneType, IsDsIntegrated, ReplicationScope
Healthy output: ZoneType should be Primary, IsDsIntegrated True, and ReplicationScope Domain. If a future part adds a second domain to this forest, Set-DnsServerPrimaryZone -Name "lab.example.com" -ReplicationScope "Forest" widens replication — not needed today.

Enabling Aging and Scavenging

Aging and scavenging are disabled by default on every Windows DNS zone and server, on purpose — Microsoft’s own guidance is to enable them only once every interval involved is understood, because a misconfigured combination deletes records that are still in use. This lab is exactly the environment scavenging is meant for: VMs get rebuilt, renamed, and re-IP’d constantly as later parts of this series experiment, and none of that cleans up its old DNS records on its own.

Zone-level aging replicates with the AD-integrated zone once enabled — set it once, on either DC, and it propagates. Server-level scavenging does not replicate; it has to be enabled individually on whichever server actually does the scavenging.

# On either DC: enable aging on the zone with the standard 7-day/7-day defaults
Set-DnsServerZoneAging -Name "lab.example.com" -Aging $true -NoRefreshInterval 7.00:00:00 -RefreshInterval 7.00:00:00 -ScavengeServers 10.10.10.10

# On dc01 only: enable server-level scavenging, since dc01 is the one designated scavenge server
Set-DnsServerScavenging -ScavengingState $true -ScavengingInterval 7.00:00:00
Important: The -ScavengeServers 10.10.10.10 parameter restricts scavenging of this zone to dc01 specifically. Microsoft’s own best-practice guidance is to designate one scavenging server per zone rather than letting every DC scavenge independently, which avoids race conditions and keeps troubleshooting to one server’s event log instead of two.

With the defaults above, a record becomes eligible for scavenging only after both the no-refresh and refresh intervals elapse — 14 days combined — and the next scavenging cycle after that actually deletes it. Records added manually rather than through dynamic updates get a zero timestamp and are excluded from scavenging entirely unless that timestamp is deliberately reset, so anything hand-entered as a static record — a future file server’s A record, say — is already safe by default.

Verifying the Configuration

Confirm both settings landed before relying on them, and check the DNS event log after the first cycle rather than assuming silence means success.

# Confirm zone aging settings and which server is designated to scavenge
Get-DnsServerZoneAging -Name "lab.example.com"

# Confirm server-level scavenging is enabled and the interval that was set
Get-DnsServerScavenging

# Force an immediate scavenging pass instead of waiting a full 7-day cycle, and see what it would remove
Start-DnsServerScavenging -Verbose
Healthy output: After a scavenging cycle runs, dc01’s DNS Server event log records event 2501 if it removed anything, or 2502 if the cycle ran but found nothing stale — both are normal. Event 4013 just means DNS is still waiting on AD DS to initialize at boot and clears on its own.

DNS Configuration Issues

Symptom Likely Cause Fix
External name resolution fails from a domain-joined VM The forwarder IP is wrong, or pfSense itself isn’t resolving externally. Run Get-DnsServerForwarder on the DC handling the query, then test pfSense’s own resolution directly before assuming the DC is at fault.
Get-DnsServerZoneAging shows Aging: False after running Set-DnsServerZoneAging The command targeted the wrong zone name, or ran against a DC that isn’t authoritative for it. Re-run against the exact zone name from Get-DnsServerZone, confirmed on dc01 specifically.
Start-DnsServerScavenging reports no eligible records even after 14+ days The stale records were added manually and carry a zero timestamp, so they’re excluded from scavenging by design. Expected behavior for static entries — only dynamically registered records age out. Confirm with Get-DnsServerResourceRecord if a record was genuinely meant to be dynamic.
Both dc01 and dc02 show scavenging enabled and event 2501 fires on both -ScavengeServers wasn’t set when aging was enabled, so every server hosting the zone scavenges independently. Re-run Set-DnsServerZoneAging -ScavengeServers 10.10.10.10 to restrict scavenging to dc01, then disable scavenging on dc02 with Set-DnsServerScavenging -ScavengingState $false on dc02 specifically.
A record that should still be live gets scavenged The combined no-refresh and refresh intervals are shorter than the client’s actual re-registration cadence — commonly a VM that was powered off for longer than 14 days. Align the intervals with how long lab VMs realistically stay offline between rebuilds, and re-add the record; it re-registers automatically once the VM is back online.

Final Thoughts

DNS in this lab now does two things it wasn’t doing after Part 4: it forwards external queries somewhere chosen on purpose, and it cleans up after itself. Neither one shows up as a visible feature — they show up as the absence of a future problem, which is exactly the kind of homelab work that’s easy to skip and expensive to have skipped once a dozen rebuilt VMs have left stale A records scattered through the zone.

What’s still missing on purpose: no certificate authority yet, so no LDAPS or internal TLS; no Group Policy beyond the domain defaults; and dc01 and dc02 still trust whatever time source they picked up during promotion, unverified. Time is next, because Part 7’s certificate authority depends on clocks staying close enough together for certificate validity checks to behave.

Key takeaway: Before moving on to Part 6, confirm Get-DnsServerForwarder shows the intended forwarder on both DCs, Get-DnsServerZoneAging -Name "lab.example.com" shows Aging: True with dc01 as the scavenge server, and Get-DnsServerScavenging on dc01 shows scavenging enabled.
Next in this series

Part 6 configures the forest’s time hierarchy on dc01 and dc02 with w32tm, points the domain at a reliable external time source, and explains why the clock skew this fixes matters more than it looks like it should — Kerberos, coming later in this series, fails outright once it drifts too far.