You manage a Windows VPS exposed on the Internet and discover in the Event Viewer thousands of failed RDP login attempts per day. This is normal: any Windows VPS with port 3389 open is constantly scanned by botnets. Without protection, a single misconfiguration is enough for an attacker to gain access.
This guide explains how to block RDP brute-force attacks at 3 levels: (1) basic RDP hygiene, (2) automatic banning of attacking IPs, (3) VPN/Gateway architecture to eliminate exposure. You will leave with a complete tested solution that is free and adaptable to any Windows Server 2019, 2022, or 2025 VPS.
Quick Summary - The Vital Minimum in 5 Minutes
- Enable NLA (Network Level Authentication): System → Remote Desktop → Advanced → require NLA
- Account lockout policy: 5 failures = block for 30 min (via
secpol.msc) - Install IPBan (1 PowerShell command, auto ban of malicious IPs):
iex (irm https://raw.githubusercontent.com/DigitalRuby/IPBan/master/IPBanCore/Windows/Scripts/install-latest.ps1)
- Change RDP port 3389 → 49xxx via
regedit(slows down automated scans).
Details and advanced options below.
Why RDP is the Number 1 Target for Botnets
The Remote Desktop Protocol is exposed on port 3389/TCP, and this port is continuously scanned across the entire IPv4 by automated botnets. A new Windows VPS brought online today without any other action will typically receive:
- 5,000 to 50,000 login attempts per day within the first few hours.
- Attempts on default accounts:
Administrator,admin,user,test,sql,backup. - Attempts on standard Windows accounts discovered via SMB / NetBIOS enumeration.
- Attempts with password lists (
rockyou.txtand derivatives), at a rate of 1 attempt every 1 to 5 seconds per IP.
If the attacker finds a weak password, they:
- Create a new "discreet" administrator account (e.g.,
Helpdesk$,IUSR_X). - Disable Windows Defender.
- Install a cryptocurrency miner, a spam bot, or ransomware.
- Sell access on black markets (e.g., historical xDedic).
The average time between compromise and first malicious impact: 15 minutes.
Hence the goal of this guide: to make your VPS uninteresting for bots and block the IPs that still try.
Diagnosing an Ongoing Brute-Force Attack
Counting Failed Attempts
Open PowerShell as an administrator and run:
# Number of login failures in the last 24 hours
Get-WinEvent -FilterHashtable @{LogName='Security'; ID=4625; StartTime=(Get-Date).AddDays(-1)} |
Measure-Object | Select-Object -ExpandProperty Count
Event ID 4625 is the Windows identifier for a failed authentication. If you see more than a few dozen per day, you are being targeted.
Viewing Attacking IPs Clearly
Get-WinEvent -FilterHashtable @{LogName='Security'; ID=4625} -MaxEvents 1000 |
ForEach-Object {
$xml = [xml]$_.ToXml()
$xml.Event.EventData.Data | Where-Object { $_.Name -eq 'IpAddress' } | Select-Object -ExpandProperty '#text'
} |
Where-Object { $_ -and $_ -ne '-' } |
Group-Object | Sort-Object Count -Descending | Select-Object -First 20
You will get the top 20 attacking IPs with their number of attempts. If an IP accumulates more than 50 attempts, it is clearly malicious - it needs to be banned.
Identifying Targeted Accounts
Get-WinEvent -FilterHashtable @{LogName='Security'; ID=4625} -MaxEvents 1000 |
ForEach-Object {
$xml = [xml]$_.ToXml()
$xml.Event.EventData.Data | Where-Object { $_.Name -eq 'TargetUserName' } | Select-Object -ExpandProperty '#text'
} |
Group-Object | Sort-Object Count -Descending | Select-Object -First 20
If you see Administrator, admin, or non-existent accounts, it is a blind brute-force attack (the most common case).
Layer 1 - RDP Hygiene (the Non-Negotiable Minimum)
Before discussing anti-brute-force tools, configure these 5 basic settings. No tool will compensate for their absence.
1.1 Enable NLA (Network Level Authentication)
NLA forces authentication BEFORE the RDP session opens. Without NLA, the attacker can launch the session, see the login screen, and brute-force much faster.
# Enable NLA via registry
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp" `
-Name "UserAuthentication" -Value 1
Or via interface: sysdm.cpl → Remote tab → check Only allow connections with NLA.
1.2 Rename (or Disable) the Administrator Account
The Administrator account represents 80% of brute-force targets. Rename it:
$adminSID = "S-1-5-21-*-500"
$adminAccount = Get-LocalUser | Where-Object { $_.SID -like $adminSID }
Rename-LocalUser -Name $adminAccount.Name -NewName "Admin_$(Get-Random -Maximum 9999)"
Better yet: create a new admin account with an unguessable name and completely disable Administrator:
Disable-LocalUser -Name "Administrator"
⚠️ Before disabling Administrator, ensure that another admin account is working and that you can connect to it via RDP. Otherwise, you will lock yourself out of the VPS.
1.3 Account Lockout Policy
5 failed attempts = account locked for 30 minutes. Configuration via secpol.msc → Account Policies → Account Lockout Policy:
| Parameter | Recommended Value |
|---|---|
| Account lockout threshold | 5 attempts |
| Account lockout duration | 30 minutes |
| Reset account lockout counter after | 30 minutes |
Or via command line:
net accounts /lockoutthreshold:5 /lockoutduration:30 /lockoutwindow:30
1.4 Strong Password Policy
net accounts /minpwlen:14 /maxpwage:90 /minpwage:1 /uniquepw:5
- Minimum 14 characters
- Expiration every 90 days
- 1 day minimum before change (anti-cycling)
- Last 5 passwords prohibited
1.5 Change the Default RDP Port (3389 → Random Port)
This does not block a targeted attacker, but eliminates 95% of the noise from automated scans.
$newPort = 53389 # Choose a random port between 49152 and 65535
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp" `
-Name "PortNumber" -Value $newPort
# Open the new port in the firewall
New-NetFirewallRule -DisplayName "RDP Custom Port" -Direction Inbound `
-LocalPort $newPort -Protocol TCP -Action Allow
# Close port 3389
Get-NetFirewallRule -DisplayName "Remote Desktop*" | Disable-NetFirewallRule
To reconnect, now use <ip>:53389 in the RDP client. Restart the server for the new port to take effect.
⚠️ Before restarting, test the new firewall rule from another IP or note the out-of-band console (KVM/IPMI console from your host) to regain access in case of error.
1.6 Limit RDP Access by IP (Firewall Scope Rules)
If you always connect from the same IPs (office, VPN, fixed IP), only allow those IPs:
# Replace with your allowed IPs (comma-separated)
$allowedIPs = "203.0.113.42", "198.51.100.10"
Get-NetFirewallRule -DisplayName "*Remote Desktop*" | ForEach-Object {
Set-NetFirewallRule -Name $_.Name -RemoteAddress $allowedIPs
}
This is the most effective measure: if the attacker does not come from a whitelisted IP, their packet is rejected before even reaching the RDP service. Brute-force impossible.
For mobile use, see layer 3 with VPN.
Layer 2 - Automatic Banning of Malicious IPs
Your basic settings are in place, but bots will continue to try. Auto-ban automatically detects IPs that multiply failures and bans them via Windows Firewall.
Comparison of the 6 Main Tools
| Tool | License | Maintenance | Type | Recommendation |
|---|---|---|---|---|
| IPBan (DigitalRuby) | Free (Pro paid) | Active 2025+ | Windows Service | ⭐ The most popular and robust |
| Fail2Ban4Win (Aldaviva) | Free, MIT | Active | Windows Service | Excellent, modern, .NET |
| EZWinBan (neil-sabol) | Free | Moderate | PS Script + Task Scheduler | Simple, ideal for small server |
| wail2ban (glasnt) | Free | Low (2017) | PS Script | Historical, to avoid |
| EvlWatcher | Free | Active | Windows Service | Good, less documented |
| RDPGuard | Commercial (~$99) | Active | Windows Service | Also covers FTP/SMTP/SQL |
Recommendation for OuiHeberg: The free version of IPBan covers 99% of needs. If you want a 100% modern open-source alternative, use Fail2Ban4Win.
IPBan Tutorial - Installation, Configuration, Verification
Step 1 - Installation in One Command
Open PowerShell as an administrator and run:
iex (irm https://raw.githubusercontent.com/DigitalRuby/IPBan/master/IPBanCore/Windows/Scripts/install-latest.ps1)
The script downloads the latest version, installs it as a Windows service, and configures the IPBan service to start automatically.
Step 2 - Check that the Service is Running
Get-Service -Name IPBan
Should display Status: Running.
Step 3 - Edit the Configuration
The configuration file is located at:
C:\Program Files\IPBan\ipban.config
Essential parameters:
<!-- Number of failures before ban -->
<add key="FailedLoginAttemptsBeforeBan" value="5"/>
<!-- Duration of the ban (in days.hours:minutes:seconds) -->
<!-- Special format: 00:30:00 = 30 minutes -->
<add key="ExpireTime" value="01:00:00:00"/> <!-- 1 day -->
<!-- Failure counting window -->
<add key="CycleTime" value="00:00:00:15"/> <!-- 15 seconds -->
<!-- Whitelist (IPs never banned) -->
<add key="Whitelist" value="203.0.113.42,198.51.100.0/24"/>
<!-- Permanent blacklist -->
<add key="Blacklist" value=""/>
To enable a strict policy (recommended for production):
FailedLoginAttemptsBeforeBan = 3ExpireTime = 30:00:00:00(30 days)- Whitelist all your management IPs.
Step 4 - Restart the Service After Modification
Restart-Service IPBan
Step 5 - View Banned IPs in Real Time
IPBan automatically creates a firewall rule named IPBan_*. To view it:
Get-NetFirewallRule -DisplayName "IPBan*" |
Get-NetFirewallAddressFilter |
Select-Object -ExpandProperty RemoteAddress
Or more visually, by looking at the IPBan logs:
Get-Content "C:\Program Files\IPBan\ipban.log" -Tail 50 -Wait
You will see live each banned IP with the timestamp and number of failures.
Step 6 - Unban an IP by Mistake
# Method 1: remove from the firewall directly
Remove-NetFirewallRule -DisplayName "IPBan_*" -ErrorAction SilentlyContinue
# Method 2: add the IP to the whitelist in ipban.config then restart
Restart-Service IPBan
Recommended Configuration by Profile
| Profile | Attempts Before Ban | Ban Duration | Cycle |
|---|---|---|---|
| Personal / dev server | 5 | 1 day | 15 sec |
| Exposed production server | 3 | 7 days | 15 sec |
| Critical server | 2 | 30 days | 15 sec |
| Bastion / single server | 1 | 365 days | 15 sec |
Alternative - Custom PowerShell Auto-Ban Script (No Dependencies)
If you want no external dependencies or third-party services, here is a standalone PowerShell script that does the job. It scans Event ID 4625 and bans any IP accumulating more than N failures over 1 hour.
Save this script in C:\Scripts\AutoBanRDP.ps1:
# === OuiHeberg - Auto-ban RDP brute-force (no dependencies) ===
param(
[int]$Threshold = 10, # Number of failures before ban
[int]$WindowHours = 1, # Counting window in hours
[int]$BanDays = 7 # Duration of the ban in days
)
$ruleName = "OuiHeberg_AutoBan_RDP"
$startTime = (Get-Date).AddHours(-$WindowHours)
# Retrieve attacking IPs
$attackers = Get-WinEvent -FilterHashtable @{
LogName = 'Security'
ID = 4625
StartTime = $startTime
} -ErrorAction SilentlyContinue | ForEach-Object {
$xml = [xml]$_.ToXml()
$ip = ($xml.Event.EventData.Data | Where-Object { $_.Name -eq 'IpAddress' }).'#text'
if ($ip -and $ip -ne '-' -and $ip -ne '::1' -and $ip -ne '127.0.0.1') { $ip }
} | Group-Object | Where-Object { $_.Count -ge $Threshold } | Select-Object -ExpandProperty Name
if (-not $attackers) {
Write-Host "No IP to ban (threshold = $Threshold over $WindowHours h)." -ForegroundColor Green
return
}
# Retrieve existing rule or create it
$existingRule = Get-NetFirewallRule -DisplayName $ruleName -ErrorAction SilentlyContinue
if ($existingRule) {
$currentIPs = ($existingRule | Get-NetFirewallAddressFilter).RemoteAddress
$allIPs = ($currentIPs + $attackers) | Sort-Object -Unique
Set-NetFirewallRule -DisplayName $ruleName -RemoteAddress $allIPs
} else {
New-NetFirewallRule -DisplayName $ruleName `
-Direction Inbound -Action Block `
-RemoteAddress $attackers `
-Description "Auto-ban OuiHeberg: created on $(Get-Date -Format 'yyyy-MM-dd HH:mm')"
}
Write-Host "$($attackers.Count) IP banned: $($attackers -join ', ')" -ForegroundColor Yellow
# Schedule automatic unban (remove rule after BanDays days)
$logPath = "C:\Scripts\AutoBanRDP.log"
$attackers | ForEach-Object {
"$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')`tBAN`t$_`tEXPIRE=$((Get-Date).AddDays($BanDays))" | Out-File -Append $logPath
}
Schedule Execution Every 15 Minutes
$action = New-ScheduledTaskAction -Execute "powershell.exe" `
-Argument "-NoProfile -ExecutionPolicy Bypass -File C:\Scripts\AutoBanRDP.ps1"
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) `
-RepetitionInterval (New-TimeSpan -Minutes 15) `
-RepetitionDuration (New-TimeSpan -Days 365)
Register-ScheduledTask -TaskName "OuiHeberg-AutoBanRDP" `
-Action $action -Trigger $trigger `
-User "SYSTEM" -RunLevel Highest -Force
The script runs every 15 minutes in the background. You can adjust $Threshold, $WindowHours, and $BanDays according to your tolerance.
Advantages of the Custom Script
- ✓ No external dependencies.
- ✓ 100% auditable and modifiable code.
- ✓ Complete local logs.
Limitations
- ❌ No automatic unban (to be scripted additionally).
- ❌ No multi-service detection (only RDP via Event 4625).
- ❌ For > 10,000 banned IPs, the firewall rule becomes heavy - prefer IPBan.
Layer 3 - Secure Architecture (the Real Solution)
Layers 1 and 2 drastically reduce risks. Real security means not exposing RDP at all. Three recommended architectures:
Option A - VPN in Front of RDP (the Simplest)
- Install a WireGuard or OpenVPN server on your VPS (or in front).
- Block the RDP port except from the internal IP of the VPN.
- You connect first via VPN, then via RDP as if you were on the LAN.
Advantages: RDP becomes invisible from the Internet. Cost: 30 min installation, native performance.
Option B - RD Gateway (Remote Desktop Gateway)
Native Microsoft solution: an RD Gateway server acts as a HTTPS proxy for your RDP sessions. The client connects via HTTPS (port 443) to the Gateway, which relays internally in RDP.
Advantages: compatible with standard RDP client, HTTPS encryption, AD integration. Disadvantage: requires an RDS license and more configuration.
Option C - Bastion / Jump Host
A minimalist "bastion" server with only SSH or RDP via key. All your connections pass through it. If the bastion is compromised, your real servers remain untouched.
Recommended for infrastructures with more than 3-5 VPS.
Verification and Monitoring
Measure Effectiveness After 24 Hours
Relaunch the diagnostic script from the previous section. If your measures are effective, you should see:
- Event 4625: < 100/day (vs thousands before).
- Top attacking IPs: 1-3 attempts each (vs hundreds).
- IPBan firewall rules: steady growth of banned IPs.
Email Alert in Case of Massive Attack
Script to schedule every hour to alert if > 500 failures/hour:
$count = (Get-WinEvent -FilterHashtable @{
LogName='Security'; ID=4625; StartTime=(Get-Date).AddHours(-1)
} -ErrorAction SilentlyContinue | Measure-Object).Count
if ($count -gt 500) {
Send-MailMessage -From "[email protected]" -To "[email protected]" `
-Subject "⚠️ Ongoing RDP Attack: $count attempts in 1h" `
-Body "VPS targeted. Check IPBan and logs." `
-SmtpServer "smtp.yourservice.com"
}
Weekly Account Audit
Once a week, check that no suspicious accounts have been created:
Get-LocalUser | Where-Object { $_.Enabled -eq $true } | Format-Table Name, LastLogon, Description
Monitor recently appeared accounts that you do not recognize.
FAQ - Securing RDP Against Brute-Force
How many RDP login attempts am I supposed to endure on a VPS?
On a newly deployed Windows VPS with the standard port 3389 exposed, expect 5,000 to 50,000 attempts per day from the first hours. This traffic comes from botnets continuously scanning IPv4. A reduction to less than 100/day is the goal after applying the guide.
What is the command to see the IPs attacking my RDP?
In admin PowerShell: Get-WinEvent -FilterHashtable @{LogName='Security'; ID=4625} -MaxEvents 1000 then extract the IpAddress fields from the XML. The complete script is in the diagnostic section. It gives you the top 20 IPs with the number of attempts.
IPBan or Fail2Ban4Win, which one to choose?
IPBan is more mature, better documented, and offers a Pro version with a dashboard. Fail2Ban4Win is more modern (.NET 6+), 100% open source MIT, and lighter. For most use cases, the free version of IPBan is more than sufficient. Fail2Ban4Win is better if you want to contribute to the code or integrate it into a CI.
Is changing the RDP port from 3389 to another enough to protect myself?
No, but it is useful as a complement. Changing the port eliminates 95% of the noise from automated scans (bots only scan 3389 by default). But a targeted attacker will perform a full scan of your ports in a few minutes. Combine custom port + NLA + lockout + IPBan for real protection.
How can I tell if my VPS has already been compromised?
Check: (1) list of local accounts (Get-LocalUser) - any account not created by you is suspicious; (2) scheduled tasks (Get-ScheduledTask) - look for unknown scripts; (3) running processes (Get-Process) - look for miners (xmrig, ethminer) or unusual outgoing connections; (4) Event Log Application/System - look for unexpected recent installations. If in serious doubt, reinstall the VPS from scratch - a compromised VPS cannot be cleaned, it must be replaced.
Does IP whitelisting work if I work remotely?
Poorly. If your IP changes regularly (3G/4G, hotels, coworking), whitelisting becomes impractical. Solution: set up a VPN on the VPS (WireGuard is ideal, 10 min to install) and allow RDP only from the internal IP of the VPN. You maintain protection without having to manage an IP list.
What to do if I am locked out after misconfiguring the firewall?
Use your host's out-of-band console (VNC, KVM, IPMI) - at OuiHeberg, accessible from the client area. This console provides direct access to the VPS without going through RDP, thus independent of the firewall. You can correct or disable the problematic rule.
How long does it take for a brute-force attack to succeed on a weak account?
With an 8-character lowercase password: a few minutes. With 12 mixed characters: a few days. With 14+ strong characters: several years. That’s why the password policy (net accounts /minpwlen:14) combined with account lockout makes brute-force practically impossible.
Does IPBan slow down my server?
No. IPBan runs as a lightweight service that reads the Event Log and adds rules to the native Windows Firewall. The consumption is negligible (< 30 MB RAM, < 1% CPU even under massive attack). Windows firewall rules are optimized to handle tens of thousands of IPs without noticeable impact.
What to do with banned IPs after several months?
You can periodically purge old IPs via IPBan (parameter ExpireTime). A window of 30 to 90 days is a good compromise: botnets regularly renew their IPs, not keeping them indefinitely prevents the list from becoming unmanageable.
Conclusion
Blocking RDP brute-force attacks on a Windows VPS requires three complementary layers: (1) RDP hygiene - NLA, lockout, strong passwords, custom port; (2) auto-ban with IPBan or a custom PowerShell script; (3) ideally, a VPN or bastion to not expose RDP at all.
With these three layers, you go from 20,000 attempts/day to less than 100, and the few IPs that still try are automatically banned. Your VPS remains easily accessible for you, but becomes an impenetrable wall for bots.
Do you want a Windows VPS already configured with IPBan, hardened firewall, and free out-of-band console included? OuiHeberg Windows VPS are preconfigured for production: NLA enabled, strict lockout policy, optional WireGuard VPN, 7/7 support based in France.
