When working with Windows DNS servers in enterprise environments, one persistent pain point is verifying whether forwarders are actually being used as configured. Unlike recursive queries where you can see the full resolution path, forwarder operations are handled internally by the DNS service with limited visibility.
Here are three technical approaches to confirm forwarder functionality:
1. DNS Debug Logging
Enable debug logging in the DNS server properties:
dnscmd /config /debuglevel 0xFFFFF
This will generate detailed logs in %systemroot%\system32\dns\dns.log
showing:
- Which forwarder was selected
- The query sent to the forwarder
- The response received
2. Network Traffic Analysis
Use a packet capture tool to observe the actual DNS traffic:
netsh trace start capture=yes scenario=NetConnection tracefile=C:\temp\dns_trace.etl
# Perform test queries
netsh trace stop
Analyze the trace with Network Monitor or Wireshark, filtering for UDP/53 traffic between your DNS server and the configured forwarders.
3. PowerShell Testing
Use Resolve-DnsName with explicit server specification to test each forwarder:
$forwarders = @('8.8.8.8','1.1.1.1')
foreach ($f in $forwarders) {
try {
$result = Resolve-DnsName -Name www.purpleflowers.com -Server $f -ErrorAction Stop
Write-Host "Forwarder $f responded successfully with IP $($result.IPAddress)"
}
catch {
Write-Warning "Forwarder $f failed: $_"
}
}
Windows DNS uses the following logic when selecting forwarders:
- Random selection from available forwarders for each new query
- If no response within 3 seconds, tries next forwarder
- After 3 failures, falls back to root hints (if forward-only isn't configured)
Here's how to interpret DNS debug logs for forwarder verification:
05/15 14:22:30 078 PACKET 000001A1D3F7B3C0 UDP Snd 192.168.1.100 53 R Q [8081 D NOERROR] A (6)google(3)com(0)
05/15 14:22:30 078 PACKET 000001A1D3F7B3C0 UDP Snd 8.8.8.8 53 R Q [8081 D NOERROR] A (6)google(3)com(0)
05/15 14:22:30 078 PACKET 000001A1D3F7B3C0 UDP Rcv 8.8.8.8 53 R Q [8081 A DR NOERROR] A (6)google(3)com(0)
This shows the server forwarding to 8.8.8.8 and receiving a response.
While basic DNS forwarding configuration is straightforward, troubleshooting resolution paths becomes critical when:
- Forwarders are configured with multiple targets (e.g., 8.8.8.8, 1.1.1.1, company-specific resolvers)
- You need to validate ECMP or load-balanced forwarder configurations
- DNSSEC validation failures occur downstream
- Latency issues emerge in hybrid environments
The most definitive method uses packet capturing. This PowerShell snippet creates an ETW trace:
# Start DNS diagnostic session
Start-NetEventSession -Name "DNSTrace" -LocalPath "C:\traces" -MaxFileSize 512
# Add DNS provider
Add-NetEventPacketCaptureProvider -SessionName "DNSTrace" -Level Verbose
# Reproduce your query (in another window)
Resolve-DnsName www.purpleflowers.com -Server YourDNSServer
# Stop and analyze
Stop-NetEventSession -Name "DNSTrace"
Analyze the resulting .etl file with Message Analyzer or convert to PCAP via etl2pcapng
for Wireshark inspection.
Enable debug logging in dnscmd:
dnscmd /config /debuglevel 0xFFFFF00
This generates verbose logs at %systemroot%\system32\dns\dns.log
showing:
- Exact forwarder IP selected
- Query timestamps with millisecond precision
- Response codes and TTL values
For ad-hoc verification without logs or captures:
# Using Windows built-in tools
Test-NetConnection -ComputerName www.purpleflowers.com -Port 53 -InformationLevel Detailed
# With DNSCmd (Windows Server)
dnscmd /statistics /clearcache
dnscmd /zonerefresh purpleflowers.com
This custom function retrieves forwarder metrics:
function Get-DNSForwarderStats {
$dns = Get-DnsServerForwarder
$stats = Get-DnsServerStatistics -Forwarder
[PSCustomObject]@{
Forwarders = $dns.IpAddress -join ", "
QueriesSent = $stats.Forwarder.QueriesSent
ResponsesReceived = $stats.Forwarder.ResponsesReceived
FailureRate = [math]::Round(($stats.Forwarder.QueriesFailed/$stats.Forwarder.QueriesSent)*100,2)
LastUsedForwarder = (Get-WinEvent -LogName "DNS Server" -MaxEvents 1 |
Where-Object {$_.Id -eq 256}).Message -replace ".*([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}).*",'$1'
}
}
Common reasons forwarder queries don't appear:
- Cached responses (check with
Show-DnsServerCache
) - Conditional forwarding rules taking precedence
- Root hints being used instead (especially for public domains)
- IPv6 forwarders configured but not testable via IPv4 tools