Fail2ban for Windows: what the real equivalents are
There is no fail2ban on Windows. Here is what the DIY PowerShell version looks like, where it breaks, and what the alternatives actually give you.
Why the question keeps coming up
On Linux, fail2ban is the reflex answer to password guessing: it tails a log, matches failure lines against a regex, and inserts a firewall rule for any address that crosses a threshold. It is small, it is standard, and it is on every server-hardening guide written in the last fifteen years.
Administrators moving to Windows go looking for the same tool and find nothing. Fail2ban is Python plus iptables and it does not run on Windows in any meaningful sense — there is no syslog to tail and no iptables to write to. What Windows has instead is the Security event log and Windows Firewall, which contain the same two ingredients, unconnected.
So the question is not really "how do I install fail2ban on Windows". It is "what connects event 4625 to a firewall rule, and how much of that do I have to build myself".
The DIY version, and what it costs
The build-it-yourself answer is a scheduled PowerShell task. The skeleton is genuinely short — this reads the last ten minutes of failures, groups them by source, and blocks anything over the threshold:
$Threshold = 10
$RuleName = 'Block-BruteForce'
$offenders = Get-WinEvent -FilterHashtable @{
LogName='Security'; Id=4625; StartTime=(Get-Date).AddMinutes(-10)
} -ErrorAction SilentlyContinue |
ForEach-Object { ([xml]$_.ToXml()).Event.EventData.Data |
Where-Object Name -eq 'IpAddress' | Select-Object -ExpandProperty '#text' } |
Where-Object { $_ -and $_ -ne '-' } |
Group-Object | Where-Object Count -ge $Threshold | Select-Object -ExpandProperty Name
$rule = Get-NetFirewallRule -DisplayName $RuleName -ErrorAction SilentlyContinue
if (-not $rule) {
New-NetFirewallRule -DisplayName $RuleName -Direction Inbound -Action Block `
-RemoteAddress $offenders | Out-Null
} else {
$existing = ($rule | Get-NetFirewallAddressFilter).RemoteAddress
$rule | Set-NetFirewallRule -RemoteAddress @($existing + $offenders | Select-Object -Unique)
}Where the script starts leaking
That thirty lines will block real attackers tonight. The gap between it and something you can leave running for a year is made of unglamorous problems, and every one of them is discovered in production:
- You will lock yourself out. There is no whitelist in the code above. The first time your own office address has a bad afternoon — a stale saved credential retrying in a loop is the classic — you are outside a server you can only reach over RDP.
- Single addresses are not enough. Botnets rotate through a range. Blocking 203.0.113.47 buys an hour before .48 starts. Blocking the /24 ends the run, but computing and merging CIDR ranges correctly is meaningfully harder than the snippet above.
- The polling window drops events. Run every ten minutes and you miss a burst that fits inside one window, or double-count across two. Under load the Security log rotates faster than your interval and events vanish before you read them.
- Nothing tells you it stopped. Scheduled tasks fail silently — after a reboot, after a password change on the account they run as, after a PowerShell policy update. You find out months later that the protection has not run since March.
- The RDP port may not be 3389. If someone moved it, the script still blocks the source but nothing verifies which ports the rule should cover, and FTP and MS SQL are not covered at all.
- Bans do not survive a rebuild. The state lives in one machine's firewall. Restore from an image and every attacker you learned about is forgotten.
What Windows gives you built in
Two native features get recommended in this context and it is worth being precise about what each one does.
Account Lockout Policy disables an account after N failed attempts. Against internet-facing RDP this is a liability more than a defence: attackers pick the account name, so anyone can lock out your Administrator account on demand, from anywhere. It is a reasonable control on internal accounts and a bad one at the perimeter.
Network Level Authentication requires the client to authenticate before a session is created. It genuinely reduces the cost of each attempt and removes a class of pre-auth exploits, and it should be on. It does not reduce the number of attempts, because the bots authenticate anyway — badly, forever.
Neither connects the event log to the firewall. That connection is the thing fail2ban actually provides, and Windows does not ship it.
The options, side by side
Ranked by what you spend and what you get:
- PowerShell script — free, full control, roughly a day to write and an ongoing obligation to maintain. Fine for one server you look at often. The failure mode is silent.
- VPN or RDP gateway in front — the strongest answer, and the most expensive in operational terms. If every user can reach a VPN, use it and stop reading. Most fleets have contractors and phones for which this never quite happens.
- IP allow-listing at the firewall — excellent when the set of legitimate addresses is small and static, unusable the moment someone works from a hotel.
- A managed agent — an installer, a whitelist that is populated before anything is blocked, subnet bans, ports detected automatically, and a console showing whether it is still running. This is the category RDP Protector is in.
What a managed agent adds over the script
Two things the script cannot have, however well you write it.
The first is the operational floor: your address whitelisted at install time, one consolidated firewall rule instead of thousands, ports detected from the registry and the listening sockets, bans that survive reboots, and a dashboard that says whether the thing is alive — which is the failure mode that actually bites.
The second is not buildable alone at all. Every protected server contributes to a shared reputation database, so an address that attacked someone else last night is already blocked when it reaches you. A script on one server can only ever learn from attacks on that server.
The decision is still made locally on the agent, so protection does not depend on the cloud being reachable — if the connection drops, the local policy keeps running.
FAQ
- Can I actually run fail2ban on Windows?
- Not usefully. Fail2ban depends on Python plus iptables and on tailing syslog-style text logs; Windows has neither iptables nor syslog, and authentication failures live in the binary Security event log. Running it under WSL protects the WSL environment, not the Windows host's RDP. What you want is a tool built on Get-WinEvent and Windows Firewall.
- Is a PowerShell script good enough for one server?
- For a single server you administer daily and check on, yes — the thirty lines above will block real attackers. Budget a day to add a whitelist, subnet handling and a health check, and accept that its failure mode is silence: scheduled tasks stop after reboots and password changes without telling anyone.
- Why is one firewall rule better than one rule per blocked IP?
- Windows Firewall evaluates rules in sequence and copes far better with a few rules holding large address lists than with thousands of small ones. Per-address rules are the standard way these scripts die: they work fine for a couple of weeks, then rule evaluation starts costing minutes and the management console becomes unusable.
- Does blocking at the firewall replace strong passwords?
- No — it removes the volume, not the requirement. Strong passwords and NLA stop the guesses that get through; firewall banning stops the guessing from reaching Windows at all, which is what protects your CPU, your audit log and your ability to find real events. Both, not either.
