Sysmon extends the Windows event log with process creation, network connection, file creation, and registry events that carry hashes, parent process lineage, and full command lines — the telemetry that native auditing omits. Deploying it on a Windows Server is straightforward; tuning it so it produces an analysable volume on a small VPS is the actual work. This guide goes from install to a working configuration with measured event rates, plus the specific detections worth keeping when you have limited disk. The approach assumes you already have operational logging discipline in place for the Windows Server infrastructure you are monitoring.
Why Sysmon and Not Just Audit Policy
- Native
4688process creation requiresAudit Process Creationplus theProcessCreationIncludeCmdLine_Enabledregistry value, and still lacks hashes and parent image paths. - Sysmon event 1 includes
Image,CommandLine,User,ParentImage,ParentCommandLine, and optionallyHashes. - Sysmon event 3 records outbound connections with process and destination — the cheapest signal for drive-by downloaders and C2 beacons.
- Events are written to
Microsoft-Windows-Sysmon/Operational, a separate channel with its own size limit, so you can size it without disturbing Security log retention.
Install and Verify
# Download to a fixed path, verify the signature, install with a config
$url = 'https://download.sysinternals.com/files/Sysmon.zip'
Invoke-WebRequest $url -OutFile C:\Tools\Sysmon.zip
Expand-Archive C:\Tools\Sysmon.zip C:\Tools\Sysmon -Force
Get-AuthenticodeSignature C:\Tools\Sysmon\Sysmon64.exe | Select Status, SignerCertificate
C:\Tools\Sysmon\Sysmon64.exe -accepteula -i C:\Tools\Sysmon\sysmon-config.xml
# Verify service and driver
Get-Service Sysmon64
fltmc filters | Select-String Sysmon
Get-WinEvent -LogName 'Microsoft-Windows-Sysmon/Operational' -MaxEvents 5 |
Select-Object TimeCreated, Id, Message
Three failure modes to expect:
- Signed driver rejected — on Secure Boot systems with HVCI, an old Sysmon build may fail to load. Use the current release; Sysmon 15.x ships a WHQL-signed driver.
- Config parse error —
-creports the hash mismatch or schema problem on stderr and the service does not start. Validate XML with[xml](Get-Content config.xml)first. - Events stop after a reboot — the service is set to Manual by the installer in some builds; set it to Automatic and add a delayed start.
A Configuration That Survives on 4 vCPU / 8 GB
The SwiftOnSecurity and Olaf Hartong configurations are excellent baselines, but both are tuned for fleets with central collection. On a single VPS, start restrictive and add.
<Sysmon schemaversion="4.90">
<EventFiltering>
<ProcessCreate onmatch="include">
<Image condition="end with">cmd.exe</Image>
<Image condition="end with">powershell.exe</Image>
<Image condition="end with">wscript.exe</Image>
<Image condition="end with">cscript.exe</Image>
<Image condition="end with">mshta.exe</Image>
<Image condition="end with">rundll32.exe</Image>
<Image condition="end with">regsvr32.exe</Image>
<Image condition="end with">certutil.exe</Image>
<ParentImage condition="end with">w3wp.exe</ParentImage>
<User condition="is">NT AUTHORITY\SYSTEM</User>
</ProcessCreate>
<NetworkConnect onmatch="exclude">
<DestinationPort condition="is">443</DestinationPort>
</NetworkConnect>
<FileCreate onmatch="include">
<TargetFilename condition="contains">\Startup\</TargetFilename>
<TargetFilename condition="contains">\Tasks\</TargetFilename>
</FileCreate>
<RegistryEvent onmatch="include">
<TargetObject condition="contains">CurrentVersion\Run</TargetObject>
</RegistryEvent>
</EventFiltering>
</Sysmon>
Update the running configuration without a service restart:
C:\Tools\Sysmon\Sysmon64.exe -c C:\Tools\Sysmon\sysmon-config.xml
Get-WinEvent -LogName 'Microsoft-Windows-Sysmon/Operational' -MaxEvents 300 |
Group-Object Id | Sort-Object Count -Descending |
Select-Object Name, Count
Measure events per hour rather than looking at totals. On an idle server, the configuration above produces under 200 events/hour; with IIS traffic and SQL, expect 1,000–3,000. If you see more than 10,000/hour, find the dominant event ID and add a targeted exclude rather than narrowing the include list — over-narrowing is how detections get silently disabled.
Log Channel Sizing and Rotation
| Metric | Idle VPS | Busy web + SQL |
|---|---|---|
| Events per hour (filtered) | ~150 | 1,000–3,000 |
| Bytes per event (avg) | ~700 B | ~900 B |
| Recommended channel max | 256 MB | 1–2 GB |
| Retention with 1 GB at 2,000 ev/h | — | ~3–4 days |
wevtutil sl "Microsoft-Windows-Sysmon/Operational" /ms:1073741824 /rt:false
Get-WinEvent -ListLog 'Microsoft-Windows-Sysmon/Operational' |
Select-Object LogName, MaximumSizeInBytes, RecordCount, FileSize
/rt:false is essential: with auto-overwrite on, events roll off silently and you may lose the window you care about. Better still, forward the channel with Windows Event Forwarding to a collector or push with fluent-bit/winlogbeat to a remote store, then keep local retention short.
Detections Worth Building First
- Office/script host spawning a shell — Sysmon 1 where
ParentImageends inw3wp.exeandImageends incmd.exe. On an IIS server this is almost always a webshell. - Base64-encoded PowerShell — Sysmon 1 regex on
-enc/-EncodedCommand, plus the existing script block log (4104) for the decoded text. - Service binary written to temp — Sysmon 11 where
TargetFilenameis under%TEMP%and ends in.exewith a signature status of Unsigned. - New outbound to uncommon port — Sysmon 3 to ports other than 80/443/53, with the process name attached.
- Hidden scheduled task — Sysmon 1/11 combined with
schtasks.exe /createand a target under\Tasks\whose XML is deleted immediately after.
Tuning the Parser Cache and Kernel Objects
Sysmon keeps an internal cache of image metadata so it does not re-parse the same binary on every process start. On a server running IIS with dozens of application pool recycles an hour, the default cache is under-sized and you will see repeated FileCreate and hash computation for the same DLLs.
<Sysmon schemaversion="4.90">
<HashAlgorithms>SHA256</HashAlgorithms>
<CheckRevocation>false</CheckRevocation>
<DnsLookup>false</DnsLookup>
<ArchiveDirectory>Sysmon\Archive</ArchiveDirectory>
</Sysmon>
CheckRevocation=false— revocation checking adds a network round trip per new binary and can stall signature checks when the CA’s CRL host is slow. Keep it false on servers.DnsLookup=false— reverse lookups on event 3 add latency to the network event path. Resolve destination addresses later, centrally.SHA256only — hashing every binary with all four algorithms quadruples the CPU cost of first-run events for no diagnostic gain.ArchiveDirectory— keeps a copy of binaries written to disk before they are deleted; useful for malicious-download forensics, but budget disk for it and exclude IIS temp directories.
Size the Sysmon driver’s memory use indirectly by watching private bytes of Sysmon64 after a week of uptime. A steady climb from 40 MB to 300 MB indicates a configuration generating large numbers of events into a queue the consumer cannot drain — either the channel write is slow or a collector is back-pressuring.
Correlating Sysmon With the Rest of the Log
Sysmon is most valuable when joined to other channels by a key. The keys that work in practice:
ProcessGuid— Sysmon’s own process identifier, stable across the life of the process. Events 1, 3, 11, 12, and 8 all carry it, so a single GUID reconstructs a process tree and its side effects.ProcessId+UtcTime— joins to Security 4688 and 4624 when you need the logon session that started the process.Hashes— joins to Defender detections (event 1116/1117) and to IOC feeds.
With a PowerShell pipeline you can reconstruct a single child process tree without any SIEM:
$since = (Get-Date).AddMinutes(-30)
$ev = Get-WinEvent -FilterHashtable @{
LogName='Microsoft-Windows-Sysmon/Operational'; Id=1; StartTime=$since
}
$ev | ForEach-Object {
$x = [xml]$_.ToXml()
[pscustomobject]@{
Time = $x.Event.System.TimeCreated.SystemTime
Parent = ($x.Event.EventData.Data | Where ParentImage).'#text'
Image = ($x.Event.EventData.Data | Where Image).'#text'
Cmd = ($x.Event.EventData.Data | Where CommandLine).'#text'
User = ($x.Event.EventData.Data | Where User).'#text'
}
} | Where-Object Parent -like '*w3wp.exe*' | Format-Table -AutoSize -Wrap
That one query answers the webshell question directly: did the IIS worker spawn a shell, and as which identity. Keep it in a script on the server so it works when the SIEM is unavailable.
Keeping It Running
- Pin the Sysmon binary version. An unannounced upgrade has changed default filtering in the past.
- Store the config in source control and deploy it to every server with
-c, so diffs are reviewable. - Alert on the service stopping:
Get-Service Sysmon64in a 5-minute scheduled task that writes to the Application log. - Review unknown-generating events monthly; each exclusion added should be justified with the process and why it is benign.
- Watch disk: Sysmon’s event file grows in the same way the Security log does, and a full system drive breaks IIS and SQL before it breaks Sysmon.
Sysmon is high-value telemetry with a bounded cost, provided you size the channel and filter before you enable it on a production box. Treat the configuration as code and the event volume as a capacity metric, and it becomes one of the more reliable detection layers available on a single Windows Server host without a full EDR subscription.



