How to Debug and Verify Windows DNS Forwarder Configuration in Production Environments


2 views

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:

  1. Random selection from available forwarders for each new query
  2. If no response within 3 seconds, tries next forwarder
  3. 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