Skip to content

Windows Event Forwarding for domain controllers

Build a Windows Event Forwarding pipeline for DCs: source-initiated subscriptions, GPO, log access, XPath queries, collector sizing and health checks.

Florian Amette7 min read

A domain controller's Security log is a ring buffer that overwrites itself, often within hours on a busy DC. An attacker with Domain Admin rights can also clear it. Any detection that depends on reading the logs on the DC itself is fragile. It depends on the event surviving, the DC being reachable, and nobody having run wevtutil cl Security. Windows Event Forwarding (WEF) is built into Windows and ships events from every DC to a central Windows Event Collector (WEC) within seconds. From there, a SIEM agent picks them up.

This guide builds a production WEF pipeline for domain controllers using source-initiated subscriptions: the collector, the GPO, Security log access, the subscription query, sizing and health monitoring. The audit policy that decides which events exist is covered in the auditing and detection pillar guide. The event IDs reference explains which ones to keep.

Architecture

  • Sources. Every DC, plus AD CS, AD FS and Entra Connect servers. The built-in forwarder runs inside the WinRM service as Network Service and pushes events over WS-Management.
  • Collector. A dedicated Windows Server running the Windows Event Collector service (Wecsvc), receiving into the ForwardedEvents log or custom channels.
  • Downstream. A SIEM agent or Azure Monitor Agent on the collector ships events on. The collector is a buffer, not the long-term store.

The collector holds a copy of every DC security event, so treat it as a sensitive system. Manage it from the same tier as the DCs, or from a dedicated security tier that DC admins cannot administer. The point is that a compromised Domain Admin cannot quietly delete the evidence. Include it in your Tier 0 inventory either way.

Measure: event volume before you build

Size from real numbers. On two or three representative DCs, measure how fast the Security log turns over:

PowerShell
$log = Get-WinEvent -ListLog Security
$first = Get-WinEvent -LogName Security -MaxEvents 1 -Oldest
$last  = Get-WinEvent -LogName Security -MaxEvents 1
$span  = ($last.TimeCreated - $first.TimeCreated).TotalSeconds
[pscustomobject]@{
  DC              = $env:COMPUTERNAME
  RecordCount     = $log.RecordCount
  HoursCovered    = [math]::Round($span / 3600, 1)
  AvgEventsPerSec = [math]::Round($log.RecordCount / $span, 1)
  LogSizeMB       = [math]::Round($log.FileSize / 1MB)
}

Measure again during the Monday morning logon peak. For planning, assume roughly 1 KB per forwarded event with ContentFormat set to Events, and multiply by the peak events per second across all DCs. That gives the ingest rate for the collector disk and the SIEM licence.

Build the collector

On a dedicated Windows Server 2022 or 2025 member server:

PowerShell
# WinRM listener (enabled by default on current Windows Server), then Wecsvc delayed auto-start
winrm quickconfig -q
wecutil qc /q

# Grow the ForwardedEvents log and move it to a dedicated volume
wevtutil sl ForwardedEvents /ms:2147483648 /lfn:"E:\Logs\ForwardedEvents.evtx"
Get-WinEvent -ListLog ForwardedEvents | Select-Object LogName, MaximumSizeInBytes, LogFilePath

Put the log on a fast dedicated volume. On Windows Server 2016 and 2019 collectors, check Microsoft's documented issue where sources cannot connect because WinRM and Wecsvc share a service host with the wrong HTTP URL ACL. Apply the documented fix before troubleshooting anything else.

Configure the sources with GPO

Create a GPO linked to the Domain Controllers OU (and to the OUs of other Tier 0 servers you forward from).

  1. Point sources at the collector. Computer Configuration > Policies > Administrative Templates > Windows Components > Event Forwarding > Configure target Subscription Manager: Enabled, with value:

    Server=http://wec01.corp.example.com:5985/wsman/SubscriptionManager/WEC,Refresh=60

  2. Make sure WinRM runs. Computer Configuration > Policies > Windows Settings > Security Settings > System Services > Windows Remote Management (WS-Management): Automatic.

  3. Let the forwarder read the Security log. The forwarder runs as Network Service, which cannot read the Security log by default. Either add NT AUTHORITY\NETWORK SERVICE to the built-in Event Log Readers group, or extend the channel ACL via Computer Configuration > Policies > Administrative Templates > Windows Components > Event Log Service > Security > Configure log access. With the second option, append (A;;0x1;;;NS) to the existing SDDL read from wevtutil gl Security.

On DCs, Event Log Readers is a domain-wide built-in group, so one membership change, run once on any DC, covers all DCs:

PowerShell
net localgroup "Event Log Readers" "NT AUTHORITY\NETWORK SERVICE" /add
  1. Allow the traffic. DCs need outbound TCP 5985 to the collector, and the collector needs inbound 5985 from DCs only. Reflect this in your DC firewall policy.

After gpupdate, restart WinRM on each DC, or reboot, so the forwarder picks up the new Event Log Readers membership.

Author the subscription

Create the subscription as XML so it is version-controlled and repeatable. The AllowedSourceDomainComputers SDDL grants the Domain Controllers group (DD) permission to use it:

XML
<Subscription xmlns="http://schemas.microsoft.com/2006/03/windows/events/subscription">
  <SubscriptionId>DC-Security</SubscriptionId>
  <SubscriptionType>SourceInitiated</SubscriptionType>
  <Description>Security events from domain controllers</Description>
  <Enabled>true</Enabled>
  <Uri>http://schemas.microsoft.com/wbem/wsman/1/windows/EventLog</Uri>
  <ConfigurationMode>Custom</ConfigurationMode>
  <Delivery Mode="Push">
    <Batching>
      <MaxItems>500</MaxItems>
      <MaxLatencyTime>30000</MaxLatencyTime>
    </Batching>
    <PushSettings>
      <Heartbeat Interval="900000"/>
    </PushSettings>
  </Delivery>
  <Query><![CDATA[
    <QueryList>
      <Query Id="0">
        <Select Path="Security">*[System[(EventID=1102 or EventID=4719 or EventID=4706 or EventID=4707 or EventID=4716)]]</Select>
        <Select Path="Security">*[System[(EventID&gt;=4720 and EventID&lt;=4767)]]</Select>
        <Select Path="Security">*[System[(EventID=4768 or EventID=4769 or EventID=4771 or EventID=4776)]]</Select>
        <Select Path="Security">*[System[(EventID=4662 or EventID=5136 or EventID=5137 or EventID=5138 or EventID=5139 or EventID=5141)]]</Select>
        <Select Path="Security">*[System[(EventID=4624 or EventID=4625 or EventID=4648 or EventID=4672 or EventID=4697)]]</Select>
        <Select Path="System">*[System[(EventID=7045)]]</Select>
        <Select Path="Directory Service">*[System[(EventID=2887 or EventID=2889 or EventID=2089)]]</Select>
      </Query>
    </QueryList>
  ]]></Query>
  <ReadExistingEvents>false</ReadExistingEvents>
  <TransportName>HTTP</TransportName>
  <ContentFormat>Events</ContentFormat>
  <Locale Language="en-US"/>
  <LogFile>ForwardedEvents</LogFile>
  <AllowedSourceNonDomainComputers></AllowedSourceNonDomainComputers>
  <AllowedSourceDomainComputers>O:NSG:BAD:P(A;;GA;;;DD)</AllowedSourceDomainComputers>
</Subscription>

Notes on the choices:

  • Custom delivery with a 30-second maximum latency is close to the built-in MinLatency mode but lets you size batches. Normal mode can delay events by up to 15 minutes, which is too slow for alerting.
  • ContentFormat Events drops the rendered message text, roughly halving storage. Your SIEM renders the message from the event data.
  • Several Select elements instead of one long or chain. Long XPath expressions hit query length limits and fail silently at the source.
  • ReadExistingEvents false avoids every DC replaying its whole log when the subscription is created. Set it to true temporarily if you need backfill.

Add a separate subscription for AD CS servers (4886 to 4900) with its own SDDL or security group, rather than widening the DC subscription.

PowerShell
wecutil cs C:\WEF\DC-Security.xml
wecutil gs DC-Security

Scale and resilience

A single collector is a single point of failure for your detection. Once the first subscription works, harden the design:

  • Two collectors, one name. Point the subscription manager GPO at a DNS alias, or list two Server= entries in the policy. A DNS alias sends each source to one collector at a time, so failover means repointing the alias, but there is no duplicate ingestion. The policy also accepts several values, and each source then forwards to every collector listed: that gives real redundancy but duplicates ingestion, so decide which matters more, availability or SIEM cost.
  • Split by volume, not by server role alone. If a handful of hub-site DCs produce most of the events, give them their own collector. A collector that falls behind shows growing gaps between TimeCreated and the time events land in ForwardedEvents.
  • Custom channels for high-volume data. Everything in ForwardedEvents is one file. Very large environments build custom event channels on the collector (one per subscription) so each can be sized and retained separately, and so a flood of 4662 events cannot push out rarer, more valuable events.
  • Ship, then trim. The collector is a buffer. Let the SIEM agent read from it continuously and keep only a few days locally. If the SIEM is down, that buffer is what saves you, so size it for your longest realistic SIEM outage.
  • Monitor the collector itself. Alert on the Windows Event Collector service stopping, on low disk space for the log volume, and on the ForwardedEvents log reaching its maximum size.

Verify

On the collector, confirm each DC is active and recent:

PowerShell
wecutil gr DC-Security          # runtime status per source: Active, heartbeat time, errors
Get-WinEvent -LogName ForwardedEvents -MaxEvents 20 |
  Select-Object TimeCreated, MachineName, Id

# DCs that have not delivered anything in the last hour
$dcs = (Get-ADDomainController -Filter *).HostName
$seen = Get-WinEvent -FilterHashtable @{ LogName='ForwardedEvents'; StartTime=(Get-Date).AddHours(-1) } |
  Select-Object -ExpandProperty MachineName -Unique
$dcs | Where-Object { $_ -notin $seen }

On a DC that is not delivering, read Microsoft-Windows-Eventlog-ForwardingPlugin/Operational. Event 100 means the subscription was created. Errors such as 104 and 105 describe connectivity or access failures, usually a firewall rule, a wrong subscription manager URL or missing Security log read access.

PowerShell
Get-WinEvent -LogName 'Microsoft-Windows-Eventlog-ForwardingPlugin/Operational' -MaxEvents 10 |
  Format-List TimeCreated, Id, Message

Then run an end-to-end test. Add and remove a test account from a monitored group, and confirm 4728 and 4729 arrive in the SIEM within your latency target. Put a scheduled version of the "silent DC" check above into monitoring. A DC that stops forwarding is itself a detection.

What it breaks

  • WinRM hardening conflicts. GPOs that disable the WinRM service on servers, or firewall baselines that block outbound 5985 from DCs, stop forwarding silently. The forwarder retries without surfacing errors in the System log.
  • Collector outage means a gap. Sources keep a bookmark and resend when the collector returns, but only while the events still exist in the local log. Keep DC Security logs large enough (several GB) to cover a collector outage of at least a day.
  • Load on DCs. The forwarder uses little CPU, but ReadExistingEvents set to true on a large log causes a burst of load and network traffic on every DC at once.
  • SIEM cost. Forwarding 4624 type 3 and 4662 from busy DCs can dominate ingest volume. Filter at the collector or SIEM only after you have seen the data. Filtering at the source loses it for good.
  • Clock and naming. MachineName in forwarded events is the source FQDN. Renamed or rebuilt DCs appear as new sources, so update any per-DC dashboards.

Related reading: feed this pipeline with decoy signals from honeytokens and deception, protect the collector as part of Tier 0 and privileged access, and see the full auditing, logging and detection area.

Frequently asked questions

Should I use source-initiated or collector-initiated subscriptions for domain controllers?

Source-initiated. The DCs learn about the collector through Group Policy and push events to it, so the collector needs no administrative rights on the DCs and new DCs join automatically when promoted into the Domain Controllers OU. Collector-initiated subscriptions require the collector to connect to each source with an account that can read the Security log, which means a credential with rights on Tier 0 living on the collector.

Is Windows Event Forwarding traffic encrypted over HTTP port 5985?

Yes, in a domain. With Kerberos authentication, WinRM encrypts the message payload even over the HTTP transport, so events are not readable on the wire. HTTPS on port 5986 with certificates is needed mainly for sources that are not domain members, or when policy requires transport-level TLS. Keep Kerberos in place and do not enable Basic authentication or AllowUnencrypted on the WinRM client or service to make forwarding work.

How many domain controllers can one Windows Event Collector handle?

Source count is rarely the limit for DCs; event rate is. A collector with fast disks and enough memory handles a few thousand events per second, and busy DCs can each produce hundreds per second at peak. Measure your events per second during Monday morning logon peaks, then size so that each collector runs well below its ceiling. Split high-volume DCs across two collectors and use more than one subscription instead of one large one.

Windows Event Forwarding for domain controllers

Related guides