How To

Configure DNS Server on Windows Server 2022

A fresh DNS Server role on Windows Server 2022 resolves internet names through root hints, at a 3,497 ms median per cold lookup on our test box. One run in five took longer than 8 seconds. Two forwarders brought the same lookups down to a 12 ms median (benchmarked October 2026 on Windows Server 2022 build 20348). That gap decides most of what you configure after the role install, so this guide measures it first and then builds the rest of the server around it.

Original content from computingforgeeks.com - post 136302

We built two Server 2022 VMs, DNS01 and DNS02, and ran every step in PowerShell, with DNS Manager screenshots where the GUI hides a setting. The walk-through covers the role install, a forward and a reverse lookup zone, records, forwarders, a conditional forwarder and aging with scavenging. Every command and output below comes from those two servers. On Windows Server 2025, follow our DNS Server guide for Server 2025 instead.

Lab layout and the measured results

The lab is two workgroup servers on one /24, both cloned from the same Windows Server 2022 Standard image with 4 vCPU and 4 GB RAM. Neither is a domain controller, so every zone here is file-backed. On a domain controller the same cmdlets work, but zones can also be stored in Active Directory, which changes replication and dynamic update options (see our Active Directory Domain Services guide).

  • DNS01 (10.0.1.10): primary for corp.example.com and the 1.0.10.in-addr.arpa reverse zone, forwards to 1.1.1.1 and 9.9.9.9.
  • DNS02 (10.0.1.11): primary for partner.example.net, which DNS01 reaches through a conditional forwarder. It also served as the client for remote queries and as the scavenging test box.
  • Gateway (10.0.1.1): plain NAT to the internet, no DNS filtering in between.

The numbers below come from that pair. Each lookup ran with both the server cache (Clear-DnsServerCache) and the client cache (Clear-DnsClientCache) emptied first, five runs per name across five names.

Measurement on DNS01Result
DNS role install, Install-WindowsFeature (DNS02)193.6 s, no restart
Cold lookup via root hints, median (25 runs)3,497 ms
Cold lookup via root hints, slowest9,564 ms; 1 of 25 lookups failed
Cold lookup via forwarders 1.1.1.1 and 9.9.9.9, median (25 runs)12 ms
Cold lookup via forwarders, slowest464 ms
Cached answer, median (5 runs)2 ms
Conditional forwarder to DNS02, first query20 ms

Root hints worked every time but one, yet a 3.5 second median is long enough for clients to notice, and a resolver that sometimes takes 9.5 seconds will trip application timeouts. The forwarder column is mostly the round trip to Cloudflare, because the forwarder answers from its own cache even when ours is empty. We would run forwarders on any Windows DNS server that has a working upstream, and keep root hints only as the fallback.

Step 1: Set a static IP and a DNS suffix

A DNS server needs an address that does not move, and it should set its own resolver to itself. We changed the address after the role install on both VMs, and each time the DNS Server log recorded event 408, “could not open socket for address”, naming the server’s own address while it briefly disappeared during the switch from DHCP to static. Do this step first and the service starts on the right address from the beginning.

Run it from the VM or hypervisor console, not over SSH or PowerShell remoting. The first command drops the leased address at once. A remote session dies before the second line runs, and the server is left with no IPv4 address. If that happens, Set-NetIPInterface -InterfaceAlias Ethernet -Dhcp Enabled from the console brings the lease back. Open PowerShell as Administrator and replace the addresses with your own:

Set-NetIPInterface -InterfaceAlias Ethernet -Dhcp Disabled
New-NetIPAddress -InterfaceAlias Ethernet -IPAddress 10.0.1.10 -PrefixLength 24 -DefaultGateway 10.0.1.1
Set-DnsClientServerAddress -InterfaceAlias Ethernet -ServerAddresses 127.0.0.1

Turning DHCP off removed the leased address on its own, so New-NetIPAddress had nothing to clash with. Check the result before you install anything, because every later step binds to this address and the origin column tells you whether the first command took effect:

Get-NetIPAddress -InterfaceAlias Ethernet -AddressFamily IPv4 | Select IPAddress,PrefixLength,PrefixOrigin

DNS01 reported a manual binding. A Dhcp origin here would mean the lease is still active and the address can change under the DNS service later:

IPAddress     PrefixLength PrefixOrigin
---------     ------------ ------------
10.0.1.10               24       Manual

The second half of this step matters on workgroup servers. A server that is not joined to a domain has no primary DNS suffix, and the DNS role writes the bare host name into the NS and SOA of every zone you create. On DNS01 that produced an NS record of dns01., a single-label name that no client outside the box can resolve. DNS02 got the suffix before its zones existed and wrote dns02.partner.example.net. plus a matching A record on its own. Set the suffix and the host name, then restart:

Set-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters' -Name 'NV Domain' -Value 'corp.example.com'
Rename-Computer -NewName DNS01 -Force
Restart-Computer

Domain members already carry the domain as their suffix, so skip the registry line on those. If you forget it on a workgroup server, the fix for zones that already exist is in Step 4.

Step 2: Install the DNS Server role on Windows Server 2022

One cmdlet installs the role, the DNS Manager console and the DnsServer PowerShell module. The Add Roles and Features wizard in Server Manager installs the same feature set if you prefer the GUI, but the cmdlet is faster to repeat on a second server:

Install-WindowsFeature -Name DNS -IncludeManagementTools

On DNS02 the install took 193.6 seconds and needed no restart, so you can add the role to a running server during working hours. The Feature Result column lists the four pieces it added, and DNS Server Tools is the one that holds both the console and the PowerShell module, which is why -IncludeManagementTools is worth keeping even on a server you plan to manage remotely:

Success Restart Needed Exit Code Feature Result
------- -------------- --------- --------------
True    No             Success   {DNS Server, Remote Server Administration Tools, DNS Server Tools, Role Administration Tools}

Run it once and wait. On DNS01 our first call was still running in the background when a second Install-WindowsFeature started, and the second one returned Success False with Exit Code Failed after 25.8 seconds even though the role finished installing. Check the real state with Get-WindowsFeature DNS,RSAT-DNS-Server before you retry anything.

The role also opens the firewall for you, so there is no need to add rules for port 53 by hand. List the rules it created anyway, because a group policy that manages the firewall can disable local rules without telling you.

Get-NetFirewallRule -DisplayGroup 'DNS Service' | Select DisplayName,Enabled,Direction,Profile,Action | Format-Table -AutoSize

Six rules arrive with the role, all enabled on every profile, including the RPC pair that DNS Manager uses for remote administration:

DisplayName                         Enabled Direction Profile Action
-----------                         ------- --------- ------- ------
All Outgoing (TCP)                     True  Outbound     Any  Allow
DNS (UDP, Incoming)                    True   Inbound     Any  Allow
RPC (TCP, Incoming)                    True   Inbound     Any  Allow
All Outgoing (UDP)                     True  Outbound     Any  Allow
DNS (TCP, Incoming)                    True   Inbound     Any  Allow
RPC Endpoint Mapper (TCP, Incoming)    True   Inbound     Any  Allow

The service listens on every address the server owns, IPv4 and IPv6 loopback included. The DnsServer module that came with it is version 2.0.0.0 and exports 134 cmdlets on this build. Zone files land in C:\Windows\System32\dns. If a firewall upstream blocks TCP 53, large answers and zone transfers fail while UDP queries still work. Open both protocols between clients and the server (our Windows Server firewall guide covers custom rules).

Step 3: Create the forward lookup zone

A forward lookup zone maps names to addresses, and it is the zone every client query lands in first. On a non-domain server it lives in a text file, and the dynamic update choice is None or NonsecureAndSecure. Secure updates exist only for Active Directory integrated zones. Create the zone with updates off for now:

Add-DnsServerPrimaryZone -Name 'corp.example.com' -ZoneFile 'corp.example.com.dns' -DynamicUpdate None -PassThru

The zone comes back as a primary with IsDsIntegrated False. That column decides which dynamic update modes the zone accepts, and the next error shows what happens when you ask for the wrong one.

ZoneName         ZoneType IsAutoCreated IsDsIntegrated IsReverseLookupZone IsSigned
--------         -------- ------------- -------------- ------------------- --------
corp.example.com Primary  False         False          False               False

Our first attempt asked for -DynamicUpdate Secure on this file-backed zone, and the cmdlet failed with WIN32 9611. The failure is only half a failure, though: the zone was created anyway with updates set to None, so re-running the corrected command then failed with WIN32 9609 because the zone already existed. This is the error pair to expect:

Add-DnsServerPrimaryZone : Failed to set property AllowUpdate for zone corp.example.com on server DNS01.
    + CategoryInfo          : InvalidArgument: (AllowUpdate:root/Microsoft/...rverPrimaryZone) [Add-DnsServerPrimaryZo
   ne], CimException
    + FullyQualifiedErrorId : WIN32 9611,Add-DnsServerPrimaryZone

Add-DnsServerPrimaryZone : Failed to create zone corp.example.com on server DNS01.
    + CategoryInfo          : ResourceExists: (corp.example.com:root/Microsoft/...rverPrimaryZone) [Add-DnsServerPrima
   ryZone], CimException
    + FullyQualifiedErrorId : WIN32 9609,Add-DnsServerPrimaryZone

If you hit 9611, check Get-DnsServerZone before you retry, because the zone is probably already there. You can keep the half-made zone and change its update setting with Set-DnsServerPrimaryZone, or remove it and start clean so the zone file matches what you meant to create. Run the next block only if you got the 9611 error and want a clean start. It deletes the zone, so run the Add-DnsServerPrimaryZone command above again afterwards:

Remove-DnsServerZone corp.example.com -Force

The DNS Manager wizard (New Zone under Forward Lookup Zones) produces the same file and asks the same questions. Our forward lookup zone walkthrough goes through every wizard page if you would rather click through it.

Step 4: Create the reverse lookup zone and fix the NS record

Reverse zones answer “which name owns 10.0.1.20”, and tools from nslookup to mail servers ask that question. The cmdlet takes the network in CIDR form and names the zone for you; for 10.0.1.0/24 the zone is 1.0.10.in-addr.arpa:

Add-DnsServerPrimaryZone -NetworkId '10.0.1.0/24' -ZoneFile '1.0.10.in-addr.arpa.dns' -DynamicUpdate None -PassThru

The new zone shows IsReverseLookupZone True, and the server derived the zone name from the network ID, so you never type the reversed octets yourself.

ZoneName            ZoneType IsAutoCreated IsDsIntegrated IsReverseLookupZone IsSigned
--------            -------- ------------- -------------- ------------------- --------
1.0.10.in-addr.arpa Primary  False         False          True                False

Networks that do not end on an octet boundary (a /22, for example) need one reverse zone per /24, or a classless delegation, which our reverse lookup zone guide covers along with the GUI wizard.

Single-label NS record on a workgroup server

DNS01 was built without the suffix from Step 1, so both of its zones were created with dns01. as the NS target and as the SOA primary server. A Resolve-DnsName corp.example.com -Type NS returned just dns01, which is a name no other machine can look up. The zones still answer queries, but a secondary server or a delegation will not find the primary. Run the fix below only if Resolve-DnsName corp.example.com -Type NS -Server 127.0.0.1 returns a bare dns01. With the suffix from Step 1 in place, the zones already carry the full name and the loop would only throw errors. It replaces the NS record, rewrites the SOA in both zones and adds the A record the new NS name needs, since a server without the suffix never creates one:

foreach($z in 'corp.example.com','1.0.10.in-addr.arpa'){
  Add-DnsServerResourceRecord -ZoneName $z -NS -Name '@' -NameServer 'dns01.corp.example.com'
  Remove-DnsServerResourceRecord -ZoneName $z -RRType NS -Name '@' -RecordData 'dns01.' -Force
  $old = Get-DnsServerResourceRecord -ZoneName $z -RRType SOA
  $new = $old.Clone()
  $new.RecordData.PrimaryServer = 'dns01.corp.example.com.'
  $new.RecordData.ResponsiblePerson = 'hostmaster.corp.example.com.'
  Set-DnsServerResourceRecord -ZoneName $z -OldInputObject $old -NewInputObject $new
}
Add-DnsServerResourceRecordA -ZoneName 'corp.example.com' -Name 'dns01' -IPv4Address '10.0.1.10'

After the loop both zones carry a fully qualified name server, and an NS query now returns dns01.corp.example.com with its address in the Additional section. We captured the forward zone after the fix, which on DNS01 ran after the Step 5 records existed. A zone fixed earlier shows a lower serial:

HostName RecordType Type Timestamp TimeToLive RecordData
-------- ---------- ---- --------- ---------- ----------
@        NS         2    0         01:00:00   dns01.corp.example.com.
@        SOA        6    0         01:00:00   [9][dns01.corp.example.com.][hostmaster.corp.example.com.]

The bracketed 9 is the SOA serial. Every record change bumps it, which is how a secondary server knows there is something new to pull (the secondary DNS server guide uses it for zone transfers).

Step 5: Add A, PTR, CNAME and MX records

With both zones in place, one A record with -CreatePtr writes the matching PTR into the reverse zone, so you never maintain the two by hand. We added two hosts, an alias and a mail exchanger. The A record for the server itself is already there. With the suffix from Step 1, creating the zone adds it automatically, as DNS02 did with dns02. Only its PTR is missing, so the last line adds it:

Add-DnsServerResourceRecordA -ZoneName 'corp.example.com' -Name 'web' -IPv4Address '10.0.1.20' -CreatePtr
Add-DnsServerResourceRecordA -ZoneName 'corp.example.com' -Name 'mail' -IPv4Address '10.0.1.25' -CreatePtr
Add-DnsServerResourceRecordCName -ZoneName 'corp.example.com' -Name 'www' -HostNameAlias 'web.corp.example.com'
Add-DnsServerResourceRecordMX -ZoneName 'corp.example.com' -Name '.' -MailExchange 'mail.corp.example.com' -Preference 10
Add-DnsServerResourceRecordPtr -ZoneName '1.0.10.in-addr.arpa' -Name '10' -PtrDomainName 'dns01.corp.example.com'

List the zone to confirm what the server stored, since the Add cmdlets print nothing unless you pass -PassThru.

Get-DnsServerResourceRecord -ZoneName 'corp.example.com' | Format-Table -AutoSize

All five records are there with a one-hour TTL and a timestamp of 0, which marks them as static. That zero matters later: scavenging never deletes a static record. The dns01. NS line is the single-label problem from Step 4, captured before the fix:

HostName RecordType Type Timestamp TimeToLive RecordData
-------- ---------- ---- --------- ---------- ----------
@        NS         2    0         01:00:00   dns01.
@        SOA        6    0         01:00:00   [6][dns01.][hostmaster.]
@        MX         15   0         01:00:00   [10][mail.corp.example.com.]
dns01    A          1    0         01:00:00   10.0.1.10
mail     A          1    0         01:00:00   10.0.1.25
web      A          1    0         01:00:00   10.0.1.20
www      CNAME      5    0         01:00:00   web.corp.example.com.

On your server the dns01 A record comes from the zone creation, or from the Step 4 fix if you skipped the suffix. Our DNS01 had neither at this point, so in the lab we added it here with -CreatePtr instead of the PTR line, which is why the listing shows it with a static timestamp. The reverse zone ends up with PTR records for .10, .20 and .25 either way. DNS Manager (dnsmgmt.msc) shows the same zone after the NS fix, with the Timestamp column reading “static” for every row:

Windows Server 2022 DNS Manager showing the corp.example.com forward lookup zone with SOA, NS, MX, A and CNAME records

Records added this way are static, so they stay until you delete them, which is what you want for servers and printers. Desktops and laptops should register themselves through dynamic updates instead, because that is the only kind of record scavenging can clean up. For SRV, TXT and other record types, and for adding records through the DNS Manager dialogs, our A and PTR record guide has the screen-by-screen version.

Step 6: Test name resolution

Test against the loopback address of the server first, because that takes the resolver settings of any client out of the picture. Resolve-DnsName follows the CNAME and returns the final A record in one call:

Resolve-DnsName www.corp.example.com -Server 127.0.0.1
Resolve-DnsName corp.example.com -Type MX -Server 127.0.0.1
Resolve-DnsName 10.0.1.20 -Server 127.0.0.1

The MX query also returns the A record for the mail host in the Additional section, and the PTR lookup confirms that -CreatePtr did its job:

Name             Type TTL  Section NameExchange          Preference
----             ---- ---  ------- ------------          ----------
corp.example.com MX   3600 Answer  mail.corp.example.com 10

Name       : mail.corp.example.com
QueryType  : A
TTL        : 3600
Section    : Additional
IP4Address : 10.0.1.25

Name                   Type TTL  Section NameHost
----                   ---- ---  ------- --------
20.1.0.10.in-addr.arpa PTR  3600 Answer  web.corp.example.com

Then query it from another machine, which proves the firewall rules and the listener on the real address. A server that answers on 127.0.0.1 but not on its LAN address usually has its Interfaces tab limited to other addresses, so this second test catches what the first one cannot. From DNS02:

Resolve-DnsName web.corp.example.com -Server 10.0.1.10
Test-NetConnection 10.0.1.10 -Port 53

Both came back clean, the A record over UDP and the TCP check on port 53. Test both, because a firewall that passes UDP 53 but drops TCP 53 lets small queries through and breaks large answers and zone transfers.

Name                 Type TTL  Section IPAddress
----                 ---- ---  ------- ---------
web.corp.example.com A    3600 Answer  10.0.1.20

ComputerName     : 10.0.1.10
RemotePort       : 53
TcpTestSucceeded : True

Clients that should use this server need it as their DNS server, either through Set-DnsClientServerAddress on each machine or, more practically, as the DNS option your Windows DHCP server hands out. The full state of DNS01 at the end of the lab, with the role, every zone and a lookup through each path, fits on one screen:

PowerShell output to configure DNS server Windows Server 2022: DNS role installed, zones listed and lookups through the zone and the conditional forwarder

The TrustAnchors zone at the bottom was not in our first zone listing and showed up after the DNS Manager session in which we browsed the console, Trust Points included. It holds DNSSEC trust anchors, is empty here, and needs nothing from you.

Step 7: Configure DNS forwarders

Forwarders send every name the server is not authoritative for to an upstream resolver instead of walking the root hints. On DNS01 that cut the median cold lookup from 3,497 ms to 12 ms. Read the current settings before you change them:

Get-DnsServerForwarder

Before you add any, the server reports an empty list, root hints on and a 3-second timeout:

UseRootHint        : True
Timeout(s)         : 3
EnableReordering   : True
IPAddress          :
ReorderedIPAddress :

Set-DnsServerForwarder replaces the whole list, while Add-DnsServerForwarder appends to it. Use the first in scripts, because running an append twice leaves you guessing which entries were there before. The list is then exactly what you typed:

Set-DnsServerForwarder -IPAddress 1.1.1.1,9.9.9.9 -PassThru

Root hints stay enabled as the fallback, which is the setting we would keep: if both forwarders go dark, the server falls back to the slow path instead of failing every external lookup:

UseRootHint        : True
Timeout(s)         : 3
EnableReordering   : True
IPAddress          : {1.1.1.1, 9.9.9.9}
ReorderedIPAddress : {1.1.1.1, 9.9.9.9}

Check that both upstreams actually answer from this server before you rely on them. Test-DnsServer with the Forwarder context sends a real query to each:

Test-DnsServer -IPAddress 1.1.1.1,9.9.9.9 -Context Forwarder

Both returned Success over UDP. The test query leaves from DNS01 itself, so this also proves that outbound UDP 53 gets through the edge firewall.

IPAddress Result  RoundTripTime TcpTried UdpTried
--------- ------  ------------- -------- --------
1.1.1.1   Success 00:00:00      False    True
9.9.9.9   Success 00:00:00      False    True

The per-name numbers show where root hints hurt, because a median hides how uneven they are. Each cell is the median of five cold lookups from DNS01, so a single slow run moves the range but not the median:

NameRoot hints, medianRoot hints, rangeForwarders, medianForwarders, range
www.debian.org5,205 ms1,063 to 8,508 ms (1 failed)18 ms12 to 246 ms
www.kernel.org4,929 ms1,012 to 9,564 ms12 ms12 to 227 ms
github.com320 ms177 to 3,497 ms12 ms11 to 14 ms
learn.microsoft.com1,653 ms1,597 to 8,095 ms12 ms12 to 464 ms
www.wikipedia.org5,346 ms711 to 8,516 ms12 ms12 to 317 ms

The spread on root hints is the real problem. github.com stayed under 400 ms after its first run. kernel.org, Microsoft Learn and Wikipedia each had a successful run past 8 seconds, and one Debian lookup failed after 8.5 seconds. No forwarder run went past 464 ms. Our first full benchmark pass, run without clearing the client cache, was misleading in the other direction: most lookups showed 11 to 12 ms because the Windows DNS client had cached them, so clear both caches whenever you measure.

In DNS Manager the same list sits on the Forwarders tab of the Properties dialog for DNS01, and the “Use root hints if no forwarders are available” box is the UseRootHint flag:

Windows Server 2022 DNS01 Properties Forwarders tab listing 1.1.1.1 and 9.9.9.9 with use root hints ticked

In corporate networks the forwarders are often the resolvers of the ISP or a filtering service instead of public resolvers. The cmdlet does not care which, but run Test-DnsServer against them before you switch, because an upstream that drops queries from your address costs the full timeout on every miss.

Step 8: Add a conditional forwarder

The target zone has to exist somewhere first. Build DNS02 with Steps 1 and 2, using 10.0.1.11, the name DNS02 and the suffix partner.example.net. On DNS02 we then created partner.example.net with one host record, running these on DNS02 itself:

Add-DnsServerPrimaryZone -Name 'partner.example.net' -ZoneFile 'partner.example.net.dns' -PassThru
Add-DnsServerResourceRecordA -ZoneName 'partner.example.net' -Name 'app' -IPv4Address '10.0.2.30'

Because DNS02 had its suffix set before this, the zone came up with dns02.partner.example.net. as its name server and an A record for DNS02, so nothing else is needed on that side. Everything from here on runs on DNS01.

A conditional forwarder sends one domain to one set of servers and leaves everything else on the normal forwarders. It is how you resolve a private zone that belongs to a partner or a lab without hosting a copy of it. Before we added one, DNS01 sent app.partner.example.net to Cloudflare like any other name, and the public resolver answered with the real example.net SOA. This is the query to compare before and after:

Resolve-DnsName app.partner.example.net -Server 127.0.0.1 -DnsOnly

Without the conditional forwarder the name does not exist, and the answer carries only the authority record:

Name        Type TTL Section   PrimaryServer           NameAdministrator  SerialNumber
----        ---- --- -------   -------------           -----------------  ------------
example.net SOA  899 Authority betty.ns.cloudflare.com dns.cloudflare.com 2418002667

On a server that is not a domain controller, leave out -ReplicationScope. That parameter belongs to Active Directory integrated zones, so the server has no directory partition to store the zone in. Passing it on DNS01 failed with WIN32 9901:

Add-DnsServerConditionalForwarderZone : Failed to create zone partner.example.net on server DNS01.
    + CategoryInfo          : ObjectNotFound: (partner.example.net:root/Microsoft/...tionalForwarder) [Add-DnsServerCo
   nditionalForwarderZone], CimException
    + FullyQualifiedErrorId : WIN32 9901,Add-DnsServerConditionalForwarderZone

Without it the zone is stored locally and points at DNS02. Read it back afterwards, because the creation cmdlet prints nothing by default.

Add-DnsServerConditionalForwarderZone -Name 'partner.example.net' -MasterServers 10.0.1.11
Get-DnsServerZone partner.example.net | Format-List ZoneName,ZoneType,IsDsIntegrated,MasterServers,ForwarderTimeout,UseRecursion

The zone type reads Forwarder, the master is DNS02, and the timeout is 3 seconds, the same as the server-level forwarders. IsDsIntegrated False means only DNS01 knows about this zone, so a second DNS server needs the same command run on it.

ZoneName         : partner.example.net
ZoneType         : Forwarder
IsDsIntegrated   : False
MasterServers    : 10.0.1.11
ForwarderTimeout : 3
UseRecursion     : False

The same name now resolves to DNS02’s record. The first query took 20 ms and the next three 1 ms each from cache. In our first run, a query sent within a second of creating the zone returned SERVFAIL once and then worked, so give a new conditional forwarder a moment before you judge it:

Name                    Type TTL  Section IPAddress
----                    ---- ---  ------- ---------
app.partner.example.net A    3600 Answer  10.0.2.30

UseRecursion False means DNS01 does not try to resolve the name itself if DNS02 fails to answer, according to the cmdlet reference. We did not take DNS02 down to test it. It is still the behaviour we want for a private zone: a partner outage should show up as a failed lookup, not as a public answer.

Step 9: Turn on aging and scavenging

Scavenging deletes dynamic records that have not been refreshed, which keeps laptops that left the network from holding stale A records for months. It is off at two levels on a fresh install, and both have to be on before anything is deleted. Check the server side first:

Get-DnsServerScavenging

The defaults on DNS01 show the task off with a zero interval, while the 7-day intervals are only the values new zones will inherit:

NoRefreshInterval  : 7.00:00:00
RefreshInterval    : 7.00:00:00
ScavengingInterval : 00:00:00
ScavengingState    : False
LastScavengeTime   :

Aging only matters for records that carry a timestamp, which means records registered through dynamic update (clients and DHCP) or added with -AgeRecord. The zone therefore needs dynamic updates turned on as well. Enable the server-side task first, then aging on the zone:

Set-DnsServerScavenging -ScavengingState $true -ScavengingInterval 7.00:00:00 -PassThru
Set-DnsServerPrimaryZone -Name corp.example.com -DynamicUpdate NonsecureAndSecure
Set-DnsServerZoneAging -Name corp.example.com -Aging $true -NoRefreshInterval 7.00:00:00 -RefreshInterval 7.00:00:00 -PassThru

The zone reports when it first becomes eligible. We enabled aging shortly after 22:00 UTC on 29 October, and the earliest scavenge landed on 5 November at 22:00, seven days out on the hour:

ZoneName             : corp.example.com
AgingEnabled         : True
AvailForScavengeTime : 11/5/2026 10:00:00 PM
RefreshInterval      : 7.00:00:00
NoRefreshInterval    : 7.00:00:00
ScavengeServers      :

Nonsecure updates on a workgroup zone let any client register any name, and DNS Manager says so with a warning on the General tab of the zone. Keep this zone on an internal network only, or move it to Active Directory and switch to secure updates. The zone-level settings live behind the Aging button on that tab:

Windows Server 2022 Zone Aging/Scavenging Properties for corp.example.com with Scavenge stale resource records ticked and 7-day intervals
  1. 1Scavenge stale resource records
  2. 2No-refresh interval
  3. 3Refresh interval
  1. Scavenge stale resource records: the per-zone switch that -Aging $true sets.
  2. No-refresh interval: 7 days during which a client re-registering the same record does not update its timestamp, which saves zone writes.
  3. Refresh interval: 7 more days in which a refresh must arrive. A record not refreshed by the end of both intervals is stale.

The dialog states the one constraint that matters: the refresh interval must be longer than the longest time a client waits between refreshing its record. If your clients register through DHCP, compare it with the longest lease you hand out before you shorten anything. The server-side half is on the Advanced tab of the same Properties dialog, as “Enable automatic scavenging of stale records” with a 7-day period:

Windows Server 2022 DNS01 Properties Advanced tab with automatic scavenging of stale records enabled and a 7-day scavenging period

The scavenging period here is how often the server runs its cleanup task, not how old a record must be. Age is decided per zone by the two intervals above, so a 7-day period with 7 plus 7 day intervals removes a dead record between 14 and 21 days after its timestamp was last updated. A refresh inside the no-refresh window does not update the timestamp. Count from the last update, not from the last time the client checked in.

What a scavenging run deleted on the test zone

Waiting 14 days was not an option, so we tested on DNS02 with 1-hour intervals, stopped the Windows Time service and moved the clock forward. The zone held three static records (app, printer and the automatic dns02), laptop-new added with -AgeRecord at about 22:06 (stored as 22:00, since timestamps are kept to the hour), and laptop-live added the same way after the clock jump. We describe that setup instead of scripting it. Moving a clock is not something to repeat on a real server. The run and the checks:

Start-DnsServerScavenging -Force
Get-WinEvent -LogName 'DNS Server' -MaxEvents 2 | Format-List TimeCreated,Id,Message
Get-DnsServerResourceRecord -ZoneName partner.example.net -RRType A

One node visited out of nine was deleted, and it was the stale laptop record. The output below is trimmed to the 2501 event and the zone listing; the second event was a routine 3150 zone write:

Id      : 2501
Message : The DNS server has completed a scavenging cycle:
              Visited Zones     = 1,
              Visited Nodes     = 9,
              Scavenged Nodes   = 1,
              Scavenged Records = 1.

              This cycle took 1 seconds.

HostName    RecordType Type Timestamp             TimeToLive RecordData
--------    ---------- ---- ---------             ---------- ----------
app         A          1    0                     01:00:00   10.0.2.30
dns02       A          1    0                     01:00:00   10.0.1.11
laptop-live A          1    10/30/2026 1:00:00 AM 01:00:00   10.0.2.53
printer     A          1    0                     01:00:00   10.0.2.60

Getting to that result exposed four behaviours that the cmdlet help does not spell out:

  • Intervals and aging in one call: setting -Aging $true together with new 1-hour intervals calculated the eligible time from the old 7-day value (5 November). Turning aging off and on again set it to the next hour. Set the intervals first if you change them.
  • Service restarts: restarting the DNS service moved AvailForScavengeTime to the restart time plus the refresh interval. A server rebooted every week for patching with a 7-day refresh interval can postpone scavenging indefinitely.
  • The 30-minute gap: three forced runs within two real minutes all logged event 2502, “no nodes were visited”, even with -Force and the clock moved hours ahead. The run that worked came 32 real minutes after the previous one, which fits the 30-minute limit that the 2502 event text lists as one of its causes.
  • Back-dating records: the TimeStamp property of a record object is read-only in PowerShell, so you cannot fake an old registration by cloning a record. Test with short intervals instead.

For production we would leave the 7-day intervals, enable scavenging on one server per zone, and only after every static record that matters has been checked for a zero timestamp. A record imported with a timestamp by mistake gets deleted on schedule, and the first anyone hears of it is a helpdesk ticket a fortnight later.

DnsServer cmdlets used in this guide

Everything above ran through the DnsServer module, so the whole server can be rebuilt from a script. These are the cmdlets the lab used and the job each one did. The full list of 134 is in the DnsServer module reference.

CmdletWhat it did here
Add-DnsServerPrimaryZoneCreated the forward zone (-Name) and reverse zone (-NetworkId)
Add-DnsServerResourceRecordAAdded hosts; -CreatePtr wrote the PTR, -AgeRecord added a timestamp
Add-DnsServerResourceRecordCName / MXAdded the alias and the mail exchanger
Add-DnsServerResourceRecordPtrAdded the PTR for the server itself
Remove-DnsServerZone / Set-DnsServerPrimaryZoneRemoved the half-made zone; switched dynamic updates on for aging
Get-DnsServerResourceRecordListed a zone with the timestamp of each record
Set-DnsServerResourceRecordRewrote the SOA from a cloned record object
Set-DnsServerForwarderReplaced the forwarder list
Test-DnsServerSent a test query to each forwarder
Add-DnsServerConditionalForwarderZoneSent partner.example.net to DNS02
Set-DnsServerScavengingTurned on the server scavenging task
Set-DnsServerZoneAgingTurned on aging for one zone
Start-DnsServerScavengingForced a scavenging run
Clear-DnsServerCacheEmptied the server cache between benchmark runs

If you are building PowerShell skills alongside the server work, our list of PowerShell scripting books and the Windows Server administration books are where we would start.

DNS Server events worth alerting on

The DNS Server event log (Get-WinEvent -LogName 'DNS Server') recorded every problem in this lab before any query failed. Mainstream support for Windows Server 2022 ended on 14 October 2026 and extended support runs to 15 October 2031 (lifecycle page), so these servers will be around for years, and these are the IDs we would alert on:

  • 408, could not open socket: logged on both servers for their own address when the IP was switched from DHCP to static after the role install. Expect it once around a readdressing; repeated 408s mean the server is bound to an address it no longer owns.
  • 2501, scavenging cycle completed: carries the counts of visited and scavenged records. A sudden jump in Scavenged Records is the first sign a static record was aged by mistake.
  • 2502, no nodes were visited: scavenging ran but no zone qualified, or a cycle already ran within 30 minutes. A week of only 2502 means aging is off on the zone you think is being cleaned.
  • 3150, zone written to file: logged each time a file-backed zone is saved to disk with a new version number, so the rate tracks how busy dynamic updates are.
  • 5504, invalid domain name in a packet: DNS01 logged 318 of these by the end of the lab, and the one we opened came from 202.12.27.33, the M root server. Only the root-hint lookups talk to root servers directly, so a server that uses forwarders should log far fewer.

A weekly Get-WinEvent -LogName 'DNS Server' | Group-Object Id gives the counts per ID in one line each, which on DNS01 was enough to spot the 5504 burst and the two 408s without opening Event Viewer.

Keep reading

Create a Bootable Windows 11 USB and Install Windows 11 Windows Create a Bootable Windows 11 USB and Install Windows 11 Set Up a Samba Share on Windows 11 and Access It from Linux Networking Set Up a Samba Share on Windows 11 and Access It from Linux Windows 11 Installation on KVM with Virt-Manager KVM Windows 11 Installation on KVM with Virt-Manager Configure NTP Server on Windows Server 2025 / 2022 (w32tm Guide) Windows Configure NTP Server on Windows Server 2025 / 2022 (w32tm Guide) Windows 11 Keyboard Shortcuts Cheat Sheet (Printable PDF) Cheat Sheets Windows 11 Keyboard Shortcuts Cheat Sheet (Printable PDF) Install and Use winget on Windows 11 and Windows Server 2025 Windows Install and Use winget on Windows 11 and Windows Server 2025

Leave a Comment

Press ESC to close