Security monitoring for small businesses requires knowing what events to watch, setting up automated alerts for the things that actually matter, and building a response process that kicks in when an alert fires. You do not need a $50,000 SIEM platform — free and open-source tools like Wazuh, Windows Event Forwarding, and custom PowerShell scripts can monitor critical events across businesses with 5 to 500 employees and alert you in real time when something is wrong.
Here is a question that keeps small business owners up at night: right now, at this exact moment, is someone trying to break into your network? The honest answer for most businesses is “I have no idea.” And that is the problem. You might have a firewall. You might have antivirus. You might even have MFA on every account. But if nobody is watching the alerts those tools generate, they are just expensive speed bumps. An attacker who triggers a firewall alert at 2 AM on a Sunday will have 16 hours of uninterrupted access before anyone checks the logs on Monday morning — if they check them at all.
Security monitoring does not require a $50,000 SIEM platform or a 24/7 security operations center. It requires knowing what to watch, setting up automated alerts for the things that actually matter, and building a response process that kicks in when an alert fires. In this guide, we are building exactly that — using free and open-source tools that work for businesses with 5 employees or 500.
What Actually Matters: The Events Worth Monitoring
Before we set up any tools, let us talk about signal versus noise. A typical Windows server generates thousands of security events per day. Most of them are routine — successful logins, normal file access, scheduled tasks running. If you try to monitor everything, you will drown in data and miss the things that matter.
Here are the security events that actually indicate something is wrong:
Critical Events (Alert Immediately)
| Event | What It Means | Why You Care |
|---|---|---|
| Multiple failed logins from one source | Brute force attack in progress | Someone is guessing passwords |
| Admin account login outside business hours | Possible compromised admin credentials | Admin accounts are the keys to everything |
| New admin account created | Attacker creating persistence | Attackers create backdoor accounts |
| Security log cleared | Someone covering their tracks | Only attackers clear security logs |
| Service installed on domain controller | Possible lateral movement | Attackers install services to maintain access |
| Firewall rule added/changed | Possible attacker creating openings | New inbound rules could expose systems |
| BitLocker suspended/disabled | Encryption protection removed | Data at rest now vulnerable |
| Antivirus disabled or tampered | Endpoint protection compromised | Attacker preparing for malware deployment |
High Priority Events (Alert Within 1 Hour)
| Event | What It Means | Why You Care |
|---|---|---|
| Account lockout | Either brute force or user issue | Distinguish between attack and forgot-password |
| Login from unusual location/IP | Possible credential theft | Especially for cloud/VPN logins |
| Privileged group membership change | Someone gaining elevated access | Should match a change request |
| File share permission change | Access control modification | Could expose sensitive data |
| Scheduled task created | New automated execution | Attackers use scheduled tasks for persistence |
| PowerShell execution policy change | Security control weakened | Could allow malicious script execution |
Medium Priority Events (Review Daily)
| Event | What It Means | Why You Care |
|---|---|---|
| Failed login attempts (low count) | Possible recon or typos | Patterns over time reveal attacks |
| Software installation | New application deployed | Unauthorized software is a risk |
| USB device connected | Removable media used | Data exfiltration or malware vector |
| Group Policy change | Configuration modification | Should match a change request |
| Certificate operations | PKI changes | Unauthorized certs enable MITM attacks |
Building Your Monitoring System: Three Tiers
Let us build this in layers, from simple to sophisticated. You do not have to implement all three — start with Tier 1 and work up.
Tier 1: PowerShell Event Monitoring (Free, Immediate)
This is what you can set up today with nothing but PowerShell and Windows Task Scheduler. It monitors the critical events listed above and sends email alerts.
<#
.SYNOPSIS
Security Event Monitor — scans Windows Security log for
suspicious events and sends email alerts.
.DESCRIPTION
Checks for brute force attempts, admin logons outside hours,
security log clearing, new admin accounts, and other critical
security events. Designed to run every 15 minutes via Task Scheduler.
#>
param(
[int]$LookbackMinutes = 15,
[int]$BruteForceThreshold = 10,
[string]$SMTPServer = "smtp.office365.com",
[int]$SMTPPort = 587,
[string]$AlertFrom = "[email protected]",
[string[]]$AlertTo = @("[email protected]"),
[string]$SMTPUser = "[email protected]",
[string]$SMTPPassword = $env:SMTP_ALERT_PASSWORD
)
$StartTime = (Get-Date).AddMinutes(-$LookbackMinutes)
$Alerts = @()
$Hostname = $env:COMPUTERNAME
# Check 1: Brute Force Detection (Event 4625)
try {
$FailedLogins = Get-WinEvent -FilterHashtable @{
LogName = 'Security'
ID = 4625
StartTime = $StartTime
} -ErrorAction SilentlyContinue
if ($FailedLogins) {
# Group by source IP
$BySource = $FailedLogins | ForEach-Object {
$xml = [xml]$_.ToXml()
[PSCustomObject]@{
Time = $_.TimeCreated
SourceIP = $xml.Event.EventData.Data |
Where-Object { $_.Name -eq 'IpAddress' } |
Select-Object -ExpandProperty '#text'
TargetUser = $xml.Event.EventData.Data |
Where-Object { $_.Name -eq 'TargetUserName' } |
Select-Object -ExpandProperty '#text'
LogonType = $xml.Event.EventData.Data |
Where-Object { $_.Name -eq 'LogonType' } |
Select-Object -ExpandProperty '#text'
}
} | Group-Object SourceIP
foreach ($Source in $BySource) {
if ($Source.Count -ge $BruteForceThreshold) {
$TargetUsers = ($Source.Group.TargetUser |
Sort-Object -Unique) -join ', '
$Alerts += [PSCustomObject]@{
Severity = "CRITICAL"
Type = "Brute Force Attack"
Details = "$($Source.Count) failed logins from " +
"$($Source.Name) targeting: $TargetUsers"
Time = ($Source.Group.Time | Sort-Object |
Select-Object -First 1).ToString('HH:mm:ss')
Action = "Block IP $($Source.Name) immediately. " +
"Check targeted accounts for compromise."
}
}
}
# Also alert on high total failed logins (distributed attack)
if ($FailedLogins.Count -ge ($BruteForceThreshold * 3)) {
$UniqueUsers = ($FailedLogins | ForEach-Object {
$xml = [xml]$_.ToXml()
$xml.Event.EventData.Data |
Where-Object { $_.Name -eq 'TargetUserName' } |
Select-Object -ExpandProperty '#text'
} | Sort-Object -Unique).Count
$Alerts += [PSCustomObject]@{
Severity = "CRITICAL"
Type = "Distributed Brute Force"
Details = "$($FailedLogins.Count) total failed logins " +
"across $UniqueUsers accounts in " +
"$LookbackMinutes minutes"
Time = $StartTime.ToString('HH:mm:ss')
Action = "Investigate immediately. Multiple accounts " +
"under attack."
}
}
}
}
catch {
Write-Warning "Failed login check error: $($_.Exception.Message)"
}
# Check 2: Admin Login Outside Business Hours
try {
$CurrentHour = (Get-Date).Hour
$IsBusinessHours = ($CurrentHour -ge 7 -and $CurrentHour -le 19)
if (-not $IsBusinessHours) {
# Check for admin logins (Event 4624 with elevated token)
$AdminLogins = Get-WinEvent -FilterHashtable @{
LogName = 'Security'
ID = 4672 # Special privileges assigned
StartTime = $StartTime
} -ErrorAction SilentlyContinue
if ($AdminLogins) {
foreach ($Login in $AdminLogins) {
$xml = [xml]$Login.ToXml()
$User = $xml.Event.EventData.Data |
Where-Object { $_.Name -eq 'SubjectUserName' } |
Select-Object -ExpandProperty '#text'
# Skip machine accounts (ending in $)
if ($User -and -not $User.EndsWith('$')) {
$Alerts += [PSCustomObject]@{
Severity = "HIGH"
Type = "After-Hours Admin Login"
Details = "Admin privileges used by '$User' " +
"at $(Get-Date -Format 'HH:mm')"
Time = $Login.TimeCreated.ToString('HH:mm:ss')
Action = "Verify this was authorized. " +
"Contact $User or their manager."
}
}
}
}
}
}
catch {
Write-Warning "Admin login check error: $($_.Exception.Message)"
}
# Check 3: Security Log Cleared (Event 1102)
try {
$LogCleared = Get-WinEvent -FilterHashtable @{
LogName = 'Security'
ID = 1102
StartTime = $StartTime
} -ErrorAction SilentlyContinue
if ($LogCleared) {
foreach ($Event in $LogCleared) {
$xml = [xml]$Event.ToXml()
$ClearedBy = $xml.Event.UserData.LogFileCleared.SubjectUserName
$Alerts += [PSCustomObject]@{
Severity = "CRITICAL"
Type = "Security Log Cleared"
Details = "Security event log was cleared by " +
"'$ClearedBy' — possible evidence destruction"
Time = $Event.TimeCreated.ToString('HH:mm:ss')
Action = "INVESTIGATE IMMEDIATELY. This is almost " +
"never legitimate. Assume compromise."
}
}
}
}
catch {
Write-Warning "Log cleared check error: $($_.Exception.Message)"
}
# Check 4: New Admin Account Created
try {
# Event 4728: Member added to security-enabled global group
# Event 4732: Member added to security-enabled local group
$GroupChanges = Get-WinEvent -FilterHashtable @{
LogName = 'Security'
ID = 4728, 4732
StartTime = $StartTime
} -ErrorAction SilentlyContinue
if ($GroupChanges) {
$AdminGroups = @('Domain Admins', 'Administrators',
'Enterprise Admins', 'Schema Admins',
'Account Operators', 'Server Operators')
foreach ($Change in $GroupChanges) {
$xml = [xml]$Change.ToXml()
$GroupName = $xml.Event.EventData.Data |
Where-Object { $_.Name -eq 'TargetUserName' } |
Select-Object -ExpandProperty '#text'
$AddedUser = $xml.Event.EventData.Data |
Where-Object { $_.Name -eq 'MemberName' } |
Select-Object -ExpandProperty '#text'
$AddedBy = $xml.Event.EventData.Data |
Where-Object { $_.Name -eq 'SubjectUserName' } |
Select-Object -ExpandProperty '#text'
if ($GroupName -in $AdminGroups) {
$Alerts += [PSCustomObject]@{
Severity = "CRITICAL"
Type = "Admin Group Membership Change"
Details = "'$AddedUser' added to '$GroupName' " +
"by '$AddedBy'"
Time = $Change.TimeCreated.ToString('HH:mm:ss')
Action = "Verify this change was authorized. " +
"If not, remove immediately and " +
"investigate $AddedBy's account."
}
}
}
}
}
catch {
Write-Warning "Admin group check error: $($_.Exception.Message)"
}
# Check 5: New Service Installed (Event 7045)
try {
$NewServices = Get-WinEvent -FilterHashtable @{
LogName = 'System'
ID = 7045
StartTime = $StartTime
} -ErrorAction SilentlyContinue
if ($NewServices) {
foreach ($Svc in $NewServices) {
$xml = [xml]$Svc.ToXml()
$ServiceName = $xml.Event.EventData.Data |
Where-Object { $_.Name -eq 'ServiceName' } |
Select-Object -ExpandProperty '#text'
$ImagePath = $xml.Event.EventData.Data |
Where-Object { $_.Name -eq 'ImagePath' } |
Select-Object -ExpandProperty '#text'
$Alerts += [PSCustomObject]@{
Severity = "HIGH"
Type = "New Service Installed"
Details = "Service '$ServiceName' installed. " +
"Path: $ImagePath"
Time = $Svc.TimeCreated.ToString('HH:mm:ss')
Action = "Verify this service is legitimate. " +
"Attackers use services for persistence."
}
}
}
}
catch {
Write-Warning "Service install check error: $($_.Exception.Message)"
}
# Check 6: Firewall Rule Changes
try {
$FirewallChanges = Get-WinEvent -FilterHashtable @{
LogName = 'Microsoft-Windows-Windows Firewall With Advanced Security/Firewall'
ID = 2004, 2005, 2006 # Rule added, modified, deleted
StartTime = $StartTime
} -ErrorAction SilentlyContinue
if ($FirewallChanges) {
$Alerts += [PSCustomObject]@{
Severity = "HIGH"
Type = "Firewall Rules Modified"
Details = "$($FirewallChanges.Count) firewall rule " +
"change(s) detected"
Time = ($FirewallChanges[0].TimeCreated).ToString('HH:mm:ss')
Action = "Review firewall changes. Unauthorized " +
"inbound rules could expose the network."
}
}
}
catch {
# This log may not exist on all systems
}
# Send Alerts
if ($Alerts.Count -gt 0) {
# Build email body
$CriticalCount = ($Alerts | Where-Object { $_.Severity -eq "CRITICAL" }).Count
$HighCount = ($Alerts | Where-Object { $_.Severity -eq "HIGH" }).Count
$Subject = if ($CriticalCount -gt 0) {
" CRITICAL Security Alert — $Hostname — $CriticalCount Critical"
} else {
" Security Alert — $Hostname — $HighCount High Priority"
}
$Body = @"
SECURITY MONITORING ALERT
Server: $Hostname
Time: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
Window: Last $LookbackMinutes minutes
Alerts: $($Alerts.Count) ($CriticalCount Critical, $HighCount High)
"@
foreach ($Alert in ($Alerts | Sort-Object Severity)) {
$Body += @"
[$($Alert.Severity)] $($Alert.Type)
Time: $($Alert.Time)
Details: $($Alert.Details)
Action: $($Alert.Action)
"@
}
$Body += "`nThis is an automated alert from the Security Event Monitor.`n"
# Send email
try {
$Credential = New-Object System.Management.Automation.PSCredential(
$SMTPUser,
(ConvertTo-SecureString $SMTPPassword -AsPlainText -Force)
)
Send-MailMessage -From $AlertFrom -To $AlertTo `
-Subject $Subject -Body $Body `
-SmtpServer $SMTPServer -Port $SMTPPort `
-UseSsl -Credential $Credential
Write-Host "Alert sent: $Subject" -ForegroundColor Red
}
catch {
Write-Warning "Failed to send alert email: $($_.Exception.Message)"
# Fallback: write to local event log
Write-EventLog -LogName Application -Source "SecurityMonitor" `
-EventId 9001 -EntryType Warning `
-Message "Security alert email failed. Alerts:`n$Body"
}
# Also log alerts locally
$AlertLogPath = "C:\IT\SecurityAlerts"
New-Item -ItemType Directory -Path $AlertLogPath -Force | Out-Null
$AlertFile = Join-Path $AlertLogPath `
"Alert_$(Get-Date -Format 'yyyyMMdd_HHmmss').json"
$Alerts | ConvertTo-Json -Depth 3 | Out-File -FilePath $AlertFile
}
else {
Write-Host "$(Get-Date -Format 'HH:mm:ss') — No alerts" -ForegroundColor Green
}
Schedule this script to run every 15 minutes with Task Scheduler:
# Create the scheduled task for security monitoring
$Action = New-ScheduledTaskAction `
-Execute "powershell.exe" `
-Argument "-ExecutionPolicy Bypass -File C:\IT\Scripts\Security-Monitor.ps1" `
-WorkingDirectory "C:\IT\Scripts"
$Trigger = New-ScheduledTaskTrigger `
-RepetitionInterval (New-TimeSpan -Minutes 15) `
-RepetitionDuration (New-TimeSpan -Days 365) `
-At "12:00AM"
$Settings = New-ScheduledTaskSettingsSet `
-AllowStartIfOnBatteries `
-DontStopIfGoingOnBatteries `
-StartWhenAvailable `
-RunOnlyIfNetworkAvailable
Register-ScheduledTask `
-TaskName "Security Event Monitor" `
-Action $Action `
-Trigger $Trigger `
-Settings $Settings `
-User "SYSTEM" `
-RunLevel Highest `
-Description "Monitors security events and sends alerts every 15 minutes"
This gives you the monitoring fundamentals. Every 15 minutes, the script scans for the critical events and emails you if it finds anything. Is it enterprise-grade? No. Will it catch a brute force attack, an unauthorized admin login, or someone clearing your security logs? Yes. And that is 95% of what matters.
Tier 2: Wazuh (Free SIEM, More Comprehensive)
When you outgrow PowerShell scripts — meaning you have multiple servers, remote workers, or compliance requirements that demand centralized logging — Wazuh is the answer. It is free, open-source, and provides capabilities that commercial SIEM tools charge $20,000+ per year for.
Wazuh gives you:
- Centralized log collection from all your Windows, Linux, and macOS endpoints
- File integrity monitoring — alerts when critical system files change
- Vulnerability detection — scans for known CVEs on your endpoints
- Compliance dashboards — pre-built reports for PCI DSS, HIPAA, NIST 800-53
- Real-time alerting with customizable rules
- A web dashboard for visual monitoring
Minimum Hardware Requirements
For a small business (5-50 endpoints), Wazuh runs on modest hardware:
| Component | Minimum | Recommended |
|---|---|---|
| CPU | 2 cores | 4 cores |
| RAM | 4 GB | 8 GB |
| Storage | 50 GB | 100 GB SSD |
| OS | Ubuntu 22.04 LTS | Ubuntu 22.04 LTS |
You can run this on an old workstation, a small VM, or a $20/month cloud instance. The point is it does not require expensive infrastructure. We cover this in more detail in Year-End IT Audit: What to Check Before January 1st.
Quick Installation
Wazuh offers a single-command installer that sets up everything — the manager, the indexer, and the dashboard:
# Download and run the Wazuh installation assistant
curl -sO https://packages.wazuh.com/4.9/wazuh-install.sh
sudo bash wazuh-install.sh -a
# The script will:
# 1. Install Wazuh indexer (stores and indexes logs)
# 2. Install Wazuh manager (processes events, applies rules)
# 3. Install Wazuh dashboard (web UI for monitoring)
# 4. Generate admin credentials (SAVE THESE)
# After installation, access the dashboard at:
# https://your-server-ip:443
# Default user: admin
# Password: displayed during installation
That is it. Seriously. One command installs a complete SIEM. The Wazuh team has made this remarkably simple for what is actually a complex system under the hood.
Enrolling Windows Agents
Once Wazuh is running, install agents on each Windows machine you want to monitor:
# Download and install the Wazuh agent on Windows
# Replace WAZUH_SERVER_IP with your Wazuh manager's IP
Invoke-WebRequest -Uri "https://packages.wazuh.com/4.x/windows/wazuh-agent-4.9.0-1.msi" `
-OutFile "$env:TEMP\wazuh-agent.msi"
# Install with your manager's address
msiexec.exe /i "$env:TEMP\wazuh-agent.msi" /q `
WAZUH_MANAGER="WAZUH_SERVER_IP" `
WAZUH_AGENT_GROUP="default"
# Start the service
Start-Service -Name WazuhSvc
# Verify the agent is connected
& "C:\Program Files (x86)\ossec-agent\agent-control.exe" -l
The agent starts sending logs to Wazuh immediately. Within minutes, you will see the endpoint appear in the Wazuh dashboard with security events, vulnerability assessments, and compliance status.
Custom Alert Rules
Wazuh comes with thousands of built-in rules, but you can add custom ones. Here is an example that alerts on the same events our PowerShell script watches, but through the Wazuh rule engine:
<!-- /var/ossec/etc/rules/local_rules.xml -->
<!-- Alert when someone clears the security log -->
<group name="custom_security,">
<rule id="100001" level="15">
<if_sid>60106</if_sid>
<description>CRITICAL: Security event log was cleared — possible evidence destruction</description>
<group>audit_cleared,security_alert,</group>
</rule>
<!-- Alert on admin group membership changes -->
<rule id="100002" level="12">
<if_sid>60144</if_sid>
<field name="win.eventdata.targetUserName">
Domain Admins|Administrators|Enterprise Admins
</field>
<description>CRITICAL: User added to admin group: $(win.eventdata.memberName)</description>
<group>admin_group_change,security_alert,</group>
</rule>
<!-- Alert on new service installation -->
<rule id="100003" level="10">
<if_sid>61106</if_sid>
<description>New service installed: $(win.eventdata.serviceName)</description>
<group>service_install,security_alert,</group>
</rule>
<!-- Brute force: 15 failed logins in 2 minutes -->
<rule id="100004" level="14" frequency="15" timeframe="120">
<if_matched_sid>60122</if_matched_sid>
<description>CRITICAL: Brute force attack detected — $(srcip)</description>
<group>brute_force,security_alert,</group>
</rule>
</group>
The level numbers control severity — Level 15 is the highest, meaning “respond immediately.” Wazuh can trigger email alerts, Slack notifications, or even automated responses (like blocking an IP) based on rule levels.
Tier 3: n8n Alert Workflow (Smart Routing)
Tier 1 and 2 get events detected and alerts sent. Tier 3 makes those alerts intelligent. Instead of dumping every alert into the same inbox, n8n routes alerts to the right person through the right channel based on severity and type.
Here is the workflow logic:
Security Event → Wazuh Alert → n8n Webhook
↓
Parse severity level
↓
CRITICAL (Level 12+):
→ Send SMS to on-call admin
→ Send Slack message to #security-alerts
→ Create ticket in ticketing system
→ Send email to IT manager
↓
HIGH (Level 8-11):
→ Send Slack message to #security-alerts
→ Send email to IT team
↓
MEDIUM (Level 5-7):
→ Log to daily digest spreadsheet
→ Send daily summary email at 8 AM
↓
LOW (Level 1-4):
→ Log only (no notification)
Wazuh Webhook Integration
Configure Wazuh to send alerts to n8n via webhook. Add this to your Wazuh manager configuration:
<!-- /var/ossec/etc/ossec.conf -->
<integration>
<name>custom-n8n</name>
<hook_url>https://your-n8n-instance.com/webhook/security-alerts</hook_url>
<level>8</level> <!-- Only send level 8+ to n8n -->
<alert_format>json</alert_format>
</integration>
Then create the integration script:
#!/usr/bin/env python3
# /var/ossec/integrations/custom-n8n
def main():
# Wazuh passes alert data via stdin
alert_file = sys.argv[1]
webhook_url = sys.argv[3]
with open(alert_file) as f:
alert = json.load(f)
# Extract key fields
payload = {
"timestamp": alert.get("timestamp", ""),
"rule_id": alert.get("rule", {}).get("id", ""),
"rule_level": alert.get("rule", {}).get("level", 0),
"rule_description": alert.get("rule", {}).get("description", ""),
"agent_name": alert.get("agent", {}).get("name", ""),
"agent_ip": alert.get("agent", {}).get("ip", ""),
"source_ip": alert.get("data", {}).get("srcip", ""),
"full_log": alert.get("full_log", "")
}
# Send to n8n webhook
try:
response = requests.post(webhook_url, json=payload, timeout=10)
sys.exit(0 if response.status_code == 200 else 1)
except Exception:
sys.exit(1)
if __name__ == "__main__":
main()
This pipes Wazuh alerts into n8n, where they get intelligently routed. A brute force attack at 3 AM sends an SMS to your on-call admin. A new service installation during business hours sends a Slack message. Low-level events get logged for the daily summary. Nobody gets woken up for a password typo, but nobody sleeps through an actual attack either.
Log Retention: How Long to Keep What
Compliance frameworks have specific log retention requirements. Here is what the major ones say:
| Framework | Minimum Retention | What to Keep |
|---|---|---|
| PCI DSS 4.0 | 12 months (3 months immediately accessible) | All security events, access logs, system events |
| HIPAA | 6 years | Access to ePHI, authentication events, system activity |
| SOC 2 | 1 year | Security events, access logs, change logs |
| NIST 800-53 | 3 years | Audit records, security events |
| FTC Safeguards | 2 years | Access to customer financial data |
For most small businesses, keeping 12 months of detailed logs and 3 years of summary reports satisfies every framework. Wazuh handles retention automatically — you configure max index size and age, and it rotates logs for you.
Setting Up Your Response Process
Monitoring without a response process is just watching your business get attacked. Here is a simple incident response procedure that works for small teams:
When an Alert Fires
- Acknowledge within 15 minutes (critical) or 1 hour (high). Someone must look at it.
- Assess: Is this a real threat or a false positive? Check the details, context, and source.
- Contain: If real, isolate the affected system. Disable the compromised account. Block the attacker’s IP.
- Investigate: What did they access? How did they get in? Are other systems affected?
- Remediate: Fix the vulnerability. Reset credentials. Restore from backup if needed.
- Document: Log everything for your compliance records and post-incident review.
On-Call Rotation
Even for a small team, define who responds to after-hours alerts:
# on-call-schedule.yaml
# Rotate weekly — the on-call person must respond to CRITICAL alerts
# within 15 minutes, day or night
schedule:
- week: 1
primary: "Alan"
phone: "386-555-0101"
escalation: "Mike"
- week: 2
primary: "Mike"
phone: "386-555-0102"
escalation: "Alan"
- week: 3
primary: "Sarah"
phone: "386-555-0103"
escalation: "Alan"
response_sla:
critical: "15 minutes"
high: "1 hour"
medium: "next business day"
What This Looks Like in Practice
Let us walk through a real scenario. It is Saturday at 2:30 AM. An attacker starts a brute force attack against your VPN login page, trying 500 password combinations per minute.
Without monitoring: The attack continues for 12 hours. Eventually a weak password is found. The attacker accesses your network, installs ransomware, and encrypts everything. You discover it Monday morning.
With Tier 1 monitoring: The PowerShell script runs at 2:30 AM, detects 10+ failed logins from a single IP, and sends you an email. You check it at 7 AM when you wake up — still a 4.5-hour gap, but you can block the IP and reset any compromised accounts before real damage occurs.
With Tier 2 + 3 monitoring: Wazuh detects the brute force within 2 minutes (rule 100004 fires after 15 failures in 120 seconds). The n8n workflow sends an SMS to the on-call admin’s phone. At 2:32 AM, the admin blocks the IP from their phone using a pre-built Wazuh active response. Total exposure: 2 minutes. The attacker gets nothing.
That is the difference monitoring makes. Not expensive monitoring. Not complicated monitoring. Just monitoring that actually works and alerts the right person.
Getting Started: Your First Week
If you did a security audit of your business and realized you have zero monitoring, here is your first-week action plan:
Day 1: Deploy the PowerShell Security Monitor script on your primary server. Schedule it for every 15 minutes. Test it by triggering a few failed logins and verifying you get the email alert.
Day 2: Enable Windows audit policies via Group Policy to ensure security events are being logged properly.
Day 3: Review the first day of alerts. Tune the brute force threshold if needed — some businesses have a lot of legitimate failed logins (forgotten passwords), and you need to set the threshold above that baseline.
Day 4-5: If you have more than 5 endpoints, spin up Wazuh on a VM or old workstation. Install agents on your servers first, workstations second.
Day 6-7: Set up your response process. Define who gets alerted, how fast they must respond, and what actions they should take. Document it. Share it with your team.
You do not need to be perfect on day one. You need to start. A basic PowerShell monitor catching brute force attacks is infinitely better than nothing. Build from there.
The Bottom Line
You do not need a six-figure SIEM platform to monitor your network. The PowerShell scripts and Wazuh deployment in this guide give you real-time visibility into the security events that actually matter, automated alerts that fire when something is wrong, and a response process that turns alerts into action. Start with the critical event monitors and expand from there.
FAQ
How much does security monitoring cost for a small business?
Using the tools in this guide, the software cost is zero. Wazuh is open source and free. PowerShell is built into Windows. n8n has a free self-hosted option. The only costs are hardware (an old workstation or $20-40/month cloud VM for Wazuh) and the time to set it up. Compare that to managed SIEM services that start at $1,000-5,000/month for Deltona businesses, and the DIY approach is compelling — especially for businesses under 50 endpoints.
Will security monitoring slow down my servers or network?
The PowerShell script runs for 5-10 seconds every 15 minutes — negligible impact. Wazuh agents use about 50-100 MB of RAM and minimal CPU on endpoints. The Wazuh server itself needs dedicated resources (4-8 GB RAM), but it runs on its own machine. Network impact is also minimal — log data is compressed and typically uses less bandwidth than a single user browsing the web.
What is the difference between a SIEM and antivirus?
Antivirus watches individual files and processes on one machine, looking for known malware. A SIEM (Security Information and Event Management) collects and correlates logs from across your entire network — servers, workstations, firewalls, applications — looking for patterns that indicate an attack. They complement each other: antivirus catches malware, SIEM catches attackers. You need both.
How do I reduce false positive alerts so I do not start ignoring them?
Start with high thresholds and lower them gradually. Set the brute force threshold to 20 failed logins instead of 5 — you will catch real attacks while ignoring forgotten-password typos. Whitelist known IP addresses (your office, VPN concentrator). Create exceptions for scheduled tasks that trigger service installation alerts. The goal is zero unnecessary alerts — every alert should require investigation. If you are getting more than 2-3 alerts per day, your thresholds are too low.
Can I outsource security monitoring instead of doing it myself?
Yes — this is what Managed Detection and Response (MDR) and Managed SIEM services provide. Companies like Arctic Wolf, Huntress, and ConnectWise offer 24/7 monitoring starting around $3-10 per endpoint per month. For a 20-person business, that is $60-200/month. The advantage is professional analysts watching your alerts around the clock. The disadvantage is cost and the fact that an external team will never know your environment as well as you do. Many businesses start with DIY monitoring and graduate to managed services as they grow.
Does Microsoft 365 have built-in security monitoring?
Yes — Microsoft Defender for Business (included in Microsoft 365 Business Premium at $22/user/month) provides endpoint detection and response, email security, and a security dashboard. For businesses already on M365 Business Premium, this is a strong option. However, it only monitors Microsoft-connected endpoints and services. If you have non-Microsoft servers, network devices, or Linux systems, you still need Wazuh or similar for comprehensive coverage.