Windows Server for beginners. Part 7: DNS in the domain
All parts of the series “Windows Server for beginners”How the DNS protocol itself works — the name tree, recursive and authoritative servers, record types, TTL — is covered in part 12 of the networking series, and we won't repeat it here. This part is about what Active Directory does with DNS: how a zone turns into directory objects, who registers records in it and how, where the server sends queries about other people's names, and which settings distinguish a domain controller's DNS from DNS running on Linux.
A reminder of why a controller is almost always a DNS server as well. Clients find controllers by SRV records (part 3), and controllers register these records themselves and refresh them every hour. A DNS server that accepts such updates, checks who sent them and replicates the zone along with the directory, requiring neither zone transfers nor separate configuration, is the Windows DNS server with a zone in Active Directory. You can keep the domain zone on BIND, and that's supported, but then dynamic updates, record permissions and replication have to be configured by hand.
Zone types
The Windows DNS server has four kinds of zones, and Get-DnsServerZone shows them all in one table:
PS C:\> Get-DnsServerZone | Format-Table ZoneName, ZoneType, IsDsIntegrated, ReplicationScope, DynamicUpdate, IsAutoCreated -AutoSize
ZoneName ZoneType IsDsIntegrated ReplicationScope DynamicUpdate IsAutoCreated
-------- -------- -------------- ---------------- ------------- -------------
_msdcs.corp.example.com Primary True Forest Secure False
0.in-addr.arpa Primary False None True
127.in-addr.arpa Primary False None True
255.in-addr.arpa Primary False None True
corp.example.com Primary True Domain Secure False
TrustAnchors Primary True Forest None False
Primary is the main zone that this server stores and accepts changes in. It's stored either in a file %SystemRoot%\System32\dns\<name>.dns (IsDsIntegrated = False; the three automatic reverse zones for 0., 127. and 255. are exactly this kind — they exist so the server doesn't go to the internet asking about localhost) or in the directory.
Secondary is a read-only copy of someone else's primary zone, obtained via zone transfer (AXFR/IXFR). In a domain it's needed when you want a controller to keep a copy of a zone owned by another DNS server, for example BIND at a hosting provider.
Stub is a truncated copy: only the SOA, NS records and the addresses of the other zone's name servers. A server with a stub zone knows whom to ask about names in it, and keeps track of changes to the NS list by itself.
Forwarder (a conditional forwarder) isn't a zone but a rule: "send queries about partner.example.net to these addresses". It's stored in the same table because, from the server's point of view, it's also delegating responsibility for a subtree.
A zone in the directory
A zone with IsDsIntegrated = True is a set of objects in one of the directory partitions, and ReplicationScope has three values:
| Scope | Partition | Where it replicates |
|---|---|---|
| Domain | DC=DomainDnsZones,DC=corp,DC=example,DC=com |
to all domain controllers of the domain that have DNS installed |
| Forest | DC=ForestDnsZones,DC=corp,DC=example,DC=com |
to all controllers in the forest with DNS |
| Legacy | CN=MicrosoftDNS,CN=System,DC=corp,DC=example,DC=com |
to all domain controllers of the domain, including those without DNS; compatibility with Windows 2000 |
That's why _msdcs.corp.example.com lives in Forest: the controller lookup records are needed by the whole forest. And the corp.example.com zone lives in Domain: controllers of other domains don't need the addresses of this domain's workstations. The scope can be changed on the fly: Set-DnsServerPrimaryZone -Name corp.example.com -ReplicationScope Forest.
Inside the partition, a zone is an object of class dnsZone, and each name is a dnsNode object with a multi-valued binary dnsRecord attribute holding all the records of that name:
PS C:\> Get-ADObject -SearchBase 'DC=corp.example.com,CN=MicrosoftDNS,DC=DomainDnsZones,DC=corp,DC=example,DC=com' -Filter 'objectClass -eq "dnsNode"' -Properties dnsRecord, whenChanged |
Select-Object Name, @{n='Records'; e={$_.dnsRecord.Count}}, whenChanged | Sort-Object Name | Select-Object -First 8
Name Records whenChanged
---- ------- -----------
@ 4 15.10.2026 9:40:18
_kerberos._tcp 1 15.10.2026 9:40:18
_ldap._tcp 1 15.10.2026 9:40:18
_msdcs 1 01.10.2026 12:05:30
dc1 1 15.10.2026 9:40:18
ForestDnsZones 1 15.10.2026 9:40:18
srv1 1 15.10.2026 8:14:40
ws1 1 15.10.2026 9:02:05
The name @ is the zone apex: the SOA, NS and A records of the name corp.example.com itself. The DnsServer module decodes what's in dnsRecord, and from here on we'll look through it; but it's useful to know that a zone is ordinary objects with everything that implies: each record has an owner and an ACL, a deleted record becomes a tombstone (dNSTombstoned = TRUE) and stays in the directory for another week, and a conflict between simultaneous edits on two controllers is resolved by directory replication rules, not DNS ones.
A consequence that explains the most common question about DNS in a domain with several sites: a record change reaches other controllers at the speed of partition replication. Within a site that's seconds; between sites it follows the site link schedule, every 15 minutes by default. Until replication completes, two controllers answer differently, and that's not an error. You can give it a targeted nudge: repadmin /replicate dc2.corp.example.com dc1.corp.example.com DC=DomainDnsZones,DC=corp,DC=example,DC=com.
Dynamic updates
Who creates the srv1 and ws1 records in the zone? The machines themselves, using the DNS dynamic update protocol (RFC 2136): the client sends the server an UPDATE message with the instruction "in the corp.example.com zone, add ws1 A 192.168.1.100". A zone with DynamicUpdate = Secure accepts such messages only when signed, and the signature is Kerberos.
The mechanism is called GSS-TSIG (RFC 3645) and works like this:
ws1 dc1 (DNS + KDC)
│ │
├─ SOA? corp.example.com ──────────────►│ 1. who is the zone's primary server
│◄─ SOA dc1.corp.example.com ───────────┤
├─ UPDATE ws1 A 192.168.1.100 ─────────►│ 2. an attempt without a signature
│◄─ REFUSED ────────────────────────────┤ the zone requires a signature
├─ TGS-REQ DNS/dc1.corp.example.com ───►│ 3. a ticket for the controller's DNS service (port 88)
│◄─ TGS-REP ────────────────────────────┤
├─ TKEY query with AP-REQ inside ──────►│ 4. establish a session key via Kerberos (port 53)
│◄─ TKEY response ──────────────────────┤
├─ UPDATE ws1 A 192.168.1.100 + TSIG ──►│ 5. the same update, signed with the session key
│◄─ NOERROR ────────────────────────────┤
The first unsigned attempt is normal Windows client behavior: it first tries the plain way and, once refused, switches to a secure update. For this the controller has the SPN DNS/dc1.corp.example.com on the DC1$ account.
A record created by a secure update gets an owner — the account whose ticket signed the request:
PS C:\> (Get-Acl 'AD:\DC=ws1,DC=corp.example.com,CN=MicrosoftDNS,DC=DomainDnsZones,DC=corp,DC=example,DC=com').Owner
CORP\WS1$
Now only WS1$, the DNS administrators and those explicitly granted the right can change or delete the ws1 record. This prevents any machine on the network from hijacking a server's name. And it's also the source of two classic problems. First: a machine was reinstalled with the same name — the new machine has a new account with a new SID, the old record belongs to the old SID, the update is rejected, and DNS keeps the old address. The fix is to delete the old record. Second: records that the DHCP server registers on behalf of clients that can't update DNS themselves; their owner is the DHCP server, and that's covered in the next part.
The three values of DynamicUpdate and when to use each:
- Secure — signed updates only; the only correct choice for the domain zone.
- NonsecureAndSecure — accept unsigned ones too. Handy for printers and Linux machines without Kerberos, but anyone on the network can overwrite any record. If you really need it, create a separate zone for such devices.
- None — records by hand only. For zones where all records are static: internal copies of public names, reverse zones without dynamic records.
What a Windows client registers and when: an A record (and AAAA) at startup, when the address changes, on Register-DnsClient, and then every 24 hours; a PTR record in the reverse zone if the zone exists. Registration is done by the DHCP Client service even with a static address, and if you disable it, the records will stop being updated. Settings: Get-DnsClient (RegisterThisConnectionsAddress, UseSuffixWhenRegistering) and Set-DnsClient.
A controller registers twice as much: its own A record like a regular client, and several dozen SRV, CNAME and A records via Netlogon. Their list is in the file %SystemRoot%\System32\config\netlogon.dns; they're refreshed every hour and on the nltest /dsregdns command. If a controller shouldn't register some records (for example, the generic _ldap._tcp.corp.example.com records for a controller in a remote site, so clients from other sites don't go to it), this is configured with the "Specify DC Locator DNS records not registered by the DCs" policy or the Netlogon service's DnsAvoidRegisterRecords registry value.
Where to send other people's queries
A workstation asks dc1 about everything: both about srv1.corp.example.com and about www.vitaliy.org. dc1 knows the first itself; for the second, it has to ask someone. There are three mechanisms.
Root hints. A file with the list of the internet's root servers (Get-DnsServerRootHint), from which the server starts recursion as described in the networking series. It works out of the box if the controller has outbound UDP and TCP 53 to the internet, and it's the least convenient option: every controller has its own cache and builds it from scratch.
Forwarders. Servers to which the controller forwards all queries it can't answer itself. Usually it's the ISP's DNS, the router or a public resolver:
PS C:\> Set-DnsServerForwarder -IPAddress 192.168.1.1, 203.0.113.53 -UseRootHint $true -Timeout 3
PS C:\> Get-DnsServerForwarder
UseRootHint : True
Timeout(s) : 3
EnableReordering : True
IPAddress : {192.168.1.1, 203.0.113.53}
ReorderedIPAddress : {192.168.1.1, 203.0.113.53}
UseRootHint $true means: if the forwarders don't answer, try the root servers; EnableReordering moves the faster responder up the list.
Conditional forwarders are forwarders for a specific domain. Send queries about partner.example.net to the partner's DNS servers, about eu.corp.example.com to the child domain's controllers, about a neighboring company's internal.example.net to its servers over a VPN:
PS C:\> Add-DnsServerConditionalForwarderZone -Name partner.example.net -MasterServers 198.51.100.53, 198.51.100.54 -ReplicationScope Forest
PS C:\> Get-DnsServerZone partner.example.net | Select-Object ZoneName, ZoneType, MasterServers, ReplicationScope
ZoneName ZoneType MasterServers ReplicationScope
-------- -------- ----------------- ----------------
partner.example.net Forwarder {198.51.100.53, 198.51.100.54} Forest
A conditional forwarder can also be stored in the directory (-ReplicationScope), and then it appears on every controller in the forest by itself. This is the basis of name resolution for trusts between forests, which we'll get to in part 22: each forest knows whom to ask about the other's names.
A stub zone solves the same problem differently: instead of "forward queries to these addresses" it's "here are the zone's NS records, ask them directly and keep track if they change". The practical difference: a conditional forwarder sends recursive queries to the specified servers, and they must support recursion; a stub zone sends iterative queries to the zone's authoritative servers, and the list of those servers updates itself. A stub is more convenient when the partner has many NS servers and they change; a conditional forwarder when queries need to go to a specific place, for example to a single address over a VPN.
PS C:\> Add-DnsServerStubZone -Name eu.corp.example.com -MasterServers 192.168.2.10 -ReplicationScope Forest
The order in which the server decides where to go: its own authoritative zone → cache → a conditional forwarder or stub for this name → forwarders → root hints. Hence a consequence: if a zone example.com is created locally in the domain (more on that below), neither forwarders nor anything else will ever be asked about example.com.
Reverse zones
Promoting a controller doesn't create reverse zones. They're needed to find a name by address: for logs, for nslookup 192.168.1.20, for sssd on Linux, for mail servers, for reports. Windows doesn't use reverse resolution for Kerberos and authentication, so a domain works without reverse zones, but life is easier with them.
PS C:\> Add-DnsServerPrimaryZone -NetworkId 192.168.1.0/24 -ReplicationScope Domain -DynamicUpdate Secure
PS C:\> Add-DnsServerPrimaryZone -NetworkId 2001:db8:1::/64 -ReplicationScope Domain -DynamicUpdate Secure
PS C:\> Get-DnsServerZone | Where-Object IsReverseLookupZone | Select-Object ZoneName, DynamicUpdate
ZoneName DynamicUpdate
-------- -------------
1.168.192.in-addr.arpa Secure
0.0.0.0.1.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa Secure
-NetworkId builds the zone name in in-addr.arpa or ip6.arpa by itself. Once the zone exists, Windows clients will add their PTR records at the next registration; the controllers' records will appear on the next nltest /dsregdns or reboot.
Aging and scavenging
Dynamic registration creates records, but who deletes them? A laptop that got an address via DHCP, registered and never appeared on the network again leaves its record forever. A year later the zone has hundreds of names pointing to addresses long since handed out to other machines.
For this, records have a timestamp (Timestamp), and there are two settings, both 7 days by default:
- No-refresh interval — the period after the last update during which re-registering with the same data doesn't change the timestamp (so daily registrations don't cause replication);
- Refresh interval — the period after that during which the record is considered alive; a client that registers within this window refreshes the timestamp.
A record not refreshed for longer than the sum of the intervals (14 days) is considered stale, and a server with scavenging enabled deletes it. It's enabled in two places: on the zone and on the server.
PS C:\> Set-DnsServerZoneAging -Name corp.example.com -Aging $true -NoRefreshInterval 7.00:00:00 -RefreshInterval 7.00:00:00
PS C:\> Set-DnsServerZoneAging -Name 1.168.192.in-addr.arpa -Aging $true
PS C:\> Set-DnsServerScavenging -ScavengingState $true -ScavengingInterval 7.00:00:00 -ApplyOnAllZones
PS C:\> Get-DnsServerScavenging
NoRefreshInterval : 7.00:00:00
RefreshInterval : 7.00:00:00
ScavengingInterval : 7.00:00:00
ScavengingState : True
LastScavengeTime : 15.10.2026 10:00:00
Scavenging is enabled on one DNS server of the domain: the zone is shared, and it's enough for one server to check it. Before enabling it, it's worth seeing what will be deleted:
PS C:\> Get-DnsServerResourceRecord -ZoneName corp.example.com -RRType A |
Where-Object { $_.Timestamp -and $_.Timestamp -lt (Get-Date).AddDays(-14) } |
Format-Table HostName, Timestamp, @{n='IP'; e={$_.RecordData.IPv4Address}}
HostName Timestamp IP
-------- --------- --
lap-old 02.08.2026 14:00:00 192.168.1.157
printer-3 15.09.2026 9:00:00 192.168.1.31
Records without a timestamp are static, and scavenging never touches them. That's the protection for servers: the A record of a server that registered itself is dynamic, and if the server is turned off for a month, the record will disappear. Records of important servers are worth making static — create them by hand with Add-DnsServerResourceRecordA -ZoneName corp.example.com -Name srv1 -IPv4Address 192.168.1.20 (without -AgeRecord the record is created without a timestamp), or clear the "Delete this record when it becomes stale" checkbox in the record's properties in the snap-in. The controllers' SRV records aren't threatened by scavenging: Netlogon refreshes them every hour.
Internal copies of public names
The domain is named corp.example.com, and the company website is www.example.com, which inside the network should open at an internal address, bypassing NAT. There are two ways.
A single-name zone (pinpoint zone): create a primary zone www.example.com with a single A record at the apex. The server will answer queries about www.example.com itself, and still send all other names in example.com to the forwarders:
PS C:\> Add-DnsServerPrimaryZone -Name www.example.com -ReplicationScope Forest -DynamicUpdate None
PS C:\> Add-DnsServerResourceRecordA -ZoneName www.example.com -Name '@' -IPv4Address 192.168.1.80
This is the best option when there are a few internal exceptions: no record of the public zone is duplicated, and the registrar remains the only place where it's edited.
A full internal copy of example.com: a primary zone with the same name as the public one and all the records needed from inside. It's mandatory if the AD domain name matches the public one (example.com instead of corp.example.com: the controllers register A records for the domain name itself, and you'll have to keep the zone anyway), and tolerable when the internal version of the site must differ from the external one entirely. The price is that every new public record (mail, autodiscover, _dmarc) has to be created twice, and half of all mysterious "doesn't work from inside, works from outside" cases come from a record someone forgot to duplicate.
A third way, since Windows Server 2016, is DNS policies: one zone, but different answers to different clients by subnet, interface or time of day (Add-DnsServerQueryResolutionPolicy). That's for cases where this same server owns the zone.
Server settings
A few parameters worth checking once:
Interfaces. By default the server listens on all addresses, including those that will appear later. If the controller has several adapters, it's worth restricting this: dnscmd /ResetListenAddresses 192.168.1.10 (Set-DnsServerSetting has this parameter too — ListeningIPAddress).
Zone transfers. For directory zones they're disabled by default (SecureSecondaries = NoTransfer), and you only need to enable them for a server outside the domain: Set-DnsServerPrimaryZone -Name corp.example.com -SecureSecondaries TransferToSecureServers -SecondaryServers 192.168.1.53.
Recursion. Enabled, and on a controller serving clients it must stay enabled. It's disabled on servers that only serve their zones to the internet; a controller shouldn't serve anything to the internet.
Global Query Block List — a list of names the server won't answer for even if a record exists: wpad and isatap. It prevents any user from registering wpad and becoming the proxy for the whole network. To check: Get-DnsServerGlobalQueryBlockList.
Cache. Get-DnsServerCache shows the settings, including LockingPercent (100: a cached record isn't overwritten by a response until its TTL expires — protection against poisoning) and MaxTtl. The contents: Show-DnsServerCache; to clear: Clear-DnsServerCache. Don't confuse it with the DNS client cache on the same machine (Get-DnsClientCache, Clear-DnsClientCache): these are two different caches.
Permissions. The DnsAdmins group manages the DNS server without being domain administrators, and this right is stronger than it looks: a DNS administrator can load their own library into the service, and the service runs as SYSTEM on the controller. Membership in DnsAdmins is the same as membership in Domain Admins.
DNSSEC. The server can sign zones (Invoke-DnsServerZoneSign) and validate signatures during recursion; the TrustAnchors zone from the first table stores the trusted keys. An internal domain usually doesn't need signing, while signature validation during recursion is enabled by default and requires only the root key, which is already in TrustAnchors.
A hands-on look
All the zone's records, with timestamps and types:
PS C:\> Get-DnsServerResourceRecord -ZoneName corp.example.com | Where-Object HostName -notlike '_*' |
Format-Table HostName, RecordType, Timestamp, TimeToLive, @{n='Data'; e={ ($_.RecordData.IPv4Address, $_.RecordData.NameServer, $_.RecordData.PrimaryServer) -ne $null | Select-Object -First 1 }} -AutoSize
HostName RecordType Timestamp TimeToLive Data
-------- ---------- --------- ---------- ----
@ A 15.10.2026 9:00:00 00:10:00 192.168.1.10
@ NS 01:00:00 dc1.corp.example.com.
@ SOA 01:00:00 dc1.corp.example.com.
dc1 A 00:20:00 192.168.1.10
DomainDnsZones A 15.10.2026 9:00:00 00:10:00 192.168.1.10
ForestDnsZones A 15.10.2026 9:00:00 00:10:00 192.168.1.10
srv1 A 15.10.2026 8:00:00 00:20:00 192.168.1.20
ws1 A 15.10.2026 9:00:00 00:20:00 192.168.1.100
@, DomainDnsZones and ForestDnsZones have timestamps — Netlogon registers them dynamically; dc1 doesn't — the promotion wizard created the controller's own A record as static. The @ record with the controller's address is the very reason the AD domain name shouldn't match the website.
A secure update in packets. Capture with pktmon filtered on port 53 while running Register-DnsClient on ws1:
No. Source Destination Protocol Info
1 192.168.1.100 192.168.1.10 DNS Standard query SOA corp.example.com
2 192.168.1.10 192.168.1.100 DNS Standard query response SOA dc1.corp.example.com
3 192.168.1.100 192.168.1.10 DNS Dynamic update 0x1a2b SOA corp.example.com A ws1.corp.example.com
4 192.168.1.10 192.168.1.100 DNS Dynamic update response 0x1a2b Refused
5 192.168.1.100 192.168.1.10 DNS Standard query TKEY 1234-ms-7.1-abcd.corp.example.com
6 192.168.1.10 192.168.1.100 DNS Standard query response TKEY
7 192.168.1.100 192.168.1.10 DNS Dynamic update 0x1a2c SOA corp.example.com A ws1.corp.example.com TSIG
8 192.168.1.10 192.168.1.100 DNS Dynamic update response 0x1a2c TSIG
The five steps from the diagram above. In packet 5, in the TKEY record, the Key Data field contains a Kerberos AP-REQ with a ticket for DNS/dc1.corp.example.com; packet 7 carries a TSIG signature with the gss-tsig algorithm. The TGS-REQ for the ticket isn't in this capture: it went over port 88 and didn't match the filter.
The server log is DNS Server in Event Viewer, or:
PS C:\> Get-WinEvent -LogName 'DNS Server' -MaxEvents 5 | Format-Table TimeCreated, Id, LevelDisplayName, Message -Wrap
Events by which problems are recognized:
| Id | What |
|---|---|
| 2 | the DNS service has started |
| 4013 | at startup DNS waits for the directory to report that initial synchronization has completed; zones from the directory aren't loaded yet. The classic sign of a controller whose DNS client points only to itself: the directory needs DNS for replication, DNS waits for the directory, and so on until the timeout. How to fix it — in the part about the second controller |
| 4015 | an error accessing the directory: the zone in the partition is unavailable |
| 4000 | the server couldn't open the directory: usually it didn't find a controller, which again means the DNS client |
| 7062 | the server is forwarding a query to itself: its own address is listed in the forwarders or NS records |
| 6527 | the zone has expired (for a secondary: it couldn't get a copy for longer than expire from the SOA) |
And the statistics that show what the server is busy with:
PS C:\> (Get-DnsServerStatistics).QueryStatistics | Select-Object TotalQueries, UdpQueries, TcpQueries, Recursions
TotalQueries UdpQueries TcpQueries Recursions
------------ ---------- ---------- ----------
184233 183904 329 61217
A third of the queries went into recursion: that's how many times clients asked about names outside the server's zones, and how many times it went to the forwarders or the cache. If this share suddenly grows, someone on the network is asking about lots of non-existent names, and Set-DnsServerDiagnostics -All $true with logging to a file will show who.
Names resolve, and the controller registers them itself. What's left is to hand out addresses to clients so they can ask anything at all.
Next — Part 8: DHCPv4. Installing the role and authorizing the server in the directory, scopes, options and reservations, fault tolerance via failover in two modes, the account for dynamic DNS updates on behalf of clients and the DnsUpdateProxy group, and how DHCP requests reach the server from another subnet.
Only registered users can leave a comment.
Sign in or register to leave a comment.