Security

Windows Server File Integrity Monitoring: Audit File System Changes

Enable Windows file system auditing, attach SACLs to critical files and folders, and read Event ID 4663 so you can see exactly who changed what on your server.

IntermediateWindows server7 min read

Windows Server file integrity monitoring is built into the operating system: you turn on the File System audit subcategory, attach a system access control list (SACL) to the files and folders that matter, and Windows writes an event to the Security log every time someone writes, deletes, renames or changes permissions on them. This guide walks through that setup on a Windows VPS and shows you how to read the resulting events without drowning in noise.

A SACL is not the same thing as NTFS permissions. Permissions decide who can touch a file; a SACL decides which of those attempts get recorded. You can audit a file that only Administrators can write to, and you can leave a world-writable folder unaudited if you don't care about it.

Before you start

You need:

  • A Windows VPS running Windows Server 2019, 2022 or 2025, with Administrator access.
  • A Remote Desktop connection. connect to your server as 203.0.113.10:3389.
  • An elevated PowerShell session. Right-click PowerShell and choose Run as administrator; Set-Acl with audit rules needs the Manage auditing and security log privilege.
  • A short list of paths to watch. Good starting points are C:\Windows\System32\drivers\etc\hosts, your IIS or application folders, C:\Windows\System32\Tasks, and any config file that controls what the server runs.
  • A decision about log volume. Auditing a whole system directory generates thousands of events per hour. Start narrow and widen later.

Warning: Do not audit C:\Windows or C:\Program Files recursively on a busy server as your first step. The Security log can roll over within minutes and you will lose the events you actually wanted.

How Windows Server file integrity monitoring works

Three pieces have to line up:

  1. The audit policy must have the File System subcategory enabled for success and failure.
  2. The object must carry a SACL entry naming the access types you want logged.
  3. The Security event log must be large enough to hold the events until you collect them.

If any one of those is missing, you get silence — no error, no warning, just no events.

Enable file system auditing

Set the subcategory locally with auditpol. Run this in an elevated Command Prompt or PowerShell:

CMD
auditpol /set /subcategory:"File System" /success:enable /failure:enable
auditpol /set /subcategory:"Handle Manipulation" /success:enable /failure:enable

Handle Manipulation is what produces the delete events (4660) that most people expect from file monitoring. Without it you see writes and permission changes but not deletions.

Confirm the result:

CMD
auditpol /get /category:"Object Access"

You should see File System and Handle Manipulation listed as Success and Failure.

If your server is domain-joined and a Group Policy object already manages audit policy, local auditpol settings are overwritten at the next policy refresh. Set the same subcategories under Computer Configuration → Policies → Windows Settings → Security Settings → Advanced Audit Policy Configuration → Audit Policies → Object Access instead. The GPO guide covers the workflow.

Attach audit rules to critical files

The GUI route is Properties → Security → Advanced → Auditing → Add. For repeatable work, use PowerShell. This example audits writes, deletes, permission changes and ownership changes on the hosts file:

PowerShell
$path = "C:\Windows\System32\drivers\etc\hosts"
$acl  = Get-Acl -Path $path -Audit
$rule = New-Object System.Security.AccessControl.FileSystemAuditRule(
    "Everyone",
    "Write,Delete,ChangePermissions,TakeOwnership",
    "None",
    "None",
    "Success,Failure"
)
$acl.AddAuditRule($rule)
Set-Acl -Path $path -AclObject $acl

For a folder, change the inheritance flags so new files inside are covered too:

PowerShell
$path = "C:\inetpub\wwwroot"
$acl  = Get-Acl -Path $path -Audit
$rule = New-Object System.Security.AccessControl.FileSystemAuditRule(
    "Everyone",
    "Write,Delete,ChangePermissions,TakeOwnership",
    "ContainerInherit,ObjectInherit",
    "None",
    "Success,Failure"
)
$acl.AddAuditRule($rule)
Set-Acl -Path $path -AclObject $acl

Auditing Everyone is normal here — the SACL only records attempts, it does not grant access. Verify what you set:

PowerShell
(Get-Acl -Path "C:\inetpub\wwwroot" -Audit).Audit | Format-List

Tip: Keep a small PowerShell script that applies your audit rules, and re-run it after an OS reinstall or when you rebuild the server. SACLs live in the file system, not in your notes.

Read the events

The event you care about most is 4663 — An attempt was made to access an object. Related IDs: 4656 (handle requested), 4660 (object deleted), 4907 (auditing settings on an object changed), 4906 (audit policy changed).

Query the last hour of writes:

PowerShell
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4663; StartTime=(Get-Date).AddHours(-1)} |
  Select-Object TimeCreated,
    @{n='User';   e={$_.Properties[1].Value}},
    @{n='Object'; e={$_.Properties[6].Value}},
    @{n='Access'; e={$_.Properties[8].Value}},
    @{n='Process';e={$_.Properties[11].Value}} |
  Format-Table -AutoSize

Read it like this: User is the account that touched the file, Object is the full path, Access lists the access mask (for example %%4417 for WriteData), and Process tells you which executable did it. A write to web.config from w3wp.exe is routine; the same write from powershell.exe at 03:00 is worth investigating.

To watch a single file continuously:

PowerShell
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4663} -MaxEvents 50 |
  Where-Object { $_.Properties[6].Value -like "*\etc\hosts" } |
  Format-List TimeCreated, Message

Keep the Security log usable

Auditing fills the log quickly. Raise the maximum size and decide what happens when it is full:

CMD
wevtutil sl Security /ms:1073741824
wevtutil sl Security /rt:false

/ms sets the maximum size in bytes (1 GB above) and /rt:false means overwrite as needed rather than stopping. If you would rather keep every event, set /rt:true and forward events off the box — but be aware that a full log means new events are dropped.

For long-term records, export to CSV on a schedule with a Task Scheduler job:

PowerShell
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4663,4660,4907; StartTime=(Get-Date).AddHours(-24)} |
  Export-Csv -Path "C:\Logs\file-audit-$(Get-Date -Format yyyyMMdd).csv" -NoTypeInformation

The Task Scheduler guide shows how to run that daily and keep the last 30 files.

Check it works

Create a test file, modify it, then look for the event:

PowerShell
"test" | Out-File C:\inetpub\wwwroot\audit-test.txt
Add-Content C:\inetpub\wwwroot\audit-test.txt "changed"
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4663; StartTime=(Get-Date).AddMinutes(-5)} |
  Where-Object { $_.Properties[6].Value -like "*audit-test.txt" } |
  Select-Object TimeCreated, @{n='User';e={$_.Properties[1].Value}}

You should see two entries, both naming the account you ran the commands as. Delete the test file afterwards and confirm a 4660 appears.

Troubleshooting

  • No 4663 events at all. Either the File System subcategory is off or the object has no SACL. Check auditpol /get /subcategory:"File System" and (Get-Acl -Path <path> -Audit).Audit. Both must return something.
  • Events appear, then stop after a reboot or gpupdate. A domain GPO is enforcing audit policy and overwriting your local settings. Move the subcategory configuration into the GPO.
  • Writes are logged but deletes are not. Enable the Handle Manipulation subcategory. Deletion is a handle operation, so it needs both subcategories.
  • Set-Acl fails with access denied. You are not elevated, or the account lacks Manage auditing and security log. Run PowerShell as Administrator.
  • The Security log fills within minutes. Your SACL is too broad. Remove inheritance on large folders, audit only the specific files that matter, and raise the log size before you widen scope again.
  • Event 4907 keeps appearing. Someone or something is changing auditing settings on an object. Treat it as a signal, not noise — check the user and process in the event before dismissing it.

Next steps

Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.