Emergency server help: get in touch

Block RDP Brute Force on Windows Server: Detect and Stop It

Stop RDP brute force on Windows Server: read event 4625 (logon types 3 and 10), close or scope port 3389, require NLA, set account lockout, and auto-block attacking IPs.

Published Updated 9 min read

Short answer: The real fix for RDP brute force is to stop exposing TCP/UDP 3389 to the internet: put RDP behind RD Gateway with MFA or a VPN, and scope the built-in “Remote Desktop” firewall rules to trusted addresses with Set-NetFirewallRule -DisplayGroup "Remote Desktop" -RemoteAddress. Then keep NLA on, set an account lockout threshold (Microsoft’s security baselines suggest 10 as a starting point), and detect attacks from Security event 4625 (logon types 3 and 10). Automatic IP blocking from 4625 with a firewall rule is a useful extra layer, not a replacement.

We ran these commands on 6 October 2026 on a Windows Server 2025 Standard domain controller (build 26100.33438, September 2026 update, Windows PowerShell 5.1) and a Windows 11 Pro member PC in our lab domain contoso.com. Where the lab result differed from the documentation, the page says so.

Confirm you are being brute forced: event 4625

Every failed logon writes event 4625 (“An account failed to log on”) to the Security log, provided failure auditing for the Logon subcategory is on. Check that first:

auditpol /get /subcategory:"Logon"

A real finding from our lab server: it had OpenSSH listening on the internet as well as RDP, and logged more than 600 failed logons in three hours. All of them were SSH password guesses, which Windows records as event 4625 with logon type 8 and no source address. The query below ignores them (it looks at types 3 and 10), and nothing can block them by IP from the event. If you run OpenSSH on Windows, set PasswordAuthentication no and KbdInteractiveAuthentication no in C:\ProgramData\ssh\sshd_config and restart sshd so only keys work.

If failures are not audited, enable them (locally with auditpol /set /subcategory:"Logon" /failure:enable, or domain-wide through Advanced Audit Policy; see our audit policy guide).

Fields that matter in 4625:

FieldMeaning for RDP
LogonType10 = RemoteInteractive (Remote Desktop). 3 = Network. With Network Level Authentication, credentials are checked before a desktop session exists, so failed RDP attempts commonly appear as type 3. Watch both.
IpAddressSource address of the attempt (the attacker, or your RD Gateway/load balancer if one sits in front).
TargetUserNameThe account name tried. Lots of different names (administrator, admin, test, scanner) means a dictionary attack.
Status / SubStatus0xC0000064 = user name does not exist (often enumeration). 0xC000006A = wrong password. 0xC0000234 = account locked out.

Top source IPs and tried usernames in the last 24 hours:

$since  = (Get-Date).AddHours(-24)
$events = Get-WinEvent -FilterHashtable @{ LogName = 'Security'; Id = 4625; StartTime = $since } -ErrorAction SilentlyContinue

$rows = foreach ($e in $events) {
    $x = [xml]$e.ToXml()
    $d = @{}; $x.Event.EventData.Data | ForEach-Object { $d[$_.Name] = $_.'#text' }
    if ($d['LogonType'] -in '3', '10') {
        [pscustomobject]@{ Time = $e.TimeCreated; IP = $d['IpAddress']; User = $d['TargetUserName']; Type = $d['LogonType']; SubStatus = $d['SubStatus'] }
    }
}
$rows | Group-Object IP   | Sort-Object Count -Descending | Select-Object -First 20 Count, Name
$rows | Group-Object User | Sort-Object Count -Descending | Select-Object -First 20 Count, Name

Hundreds or thousands of failures per day from addresses you do not recognise, against generic usernames, is a brute-force or password-spray attack. For the successful side of the story (who actually connected), see RDP connection logs: 14 event IDs.

Step 1: stop exposing RDP to the internet

Brute force only works when attackers can reach the port. Pick one of these and close 3389 on the edge firewall:

  • RD Gateway: users connect over HTTPS (TCP 443) to the gateway, which proxies RDP to internal hosts. Add MFA (for example with the NPS extension for Microsoft Entra multifactor authentication) and limit which users and hosts the gateway allows through its connection and resource authorization policies. Our RDS farm on Windows Server 2025 guide includes the gateway role.
  • VPN: RDP only after the user has connected to a VPN with MFA.
  • Cloud-managed access: on Azure, use Azure Bastion or just-in-time VM access instead of a public RDP port.

If a server must stay reachable directly for a while, at least scope the built-in Remote Desktop rules to known source addresses (an office IP, a jump host):

Get-NetFirewallRule -DisplayGroup "Remote Desktop" | Format-Table DisplayName, Enabled, Profile, Direction
Set-NetFirewallRule -DisplayGroup "Remote Desktop" -RemoteAddress 203.0.113.10, 198.51.100.0/24

Scoping the rule to the wrong address locks you out of RDP. Before you run it on a remote server, confirm your own public IP is in the list and that you have console or out-of-band access (iLO, iDRAC, hypervisor console) as a fallback.

Step 2: require NLA and set an account lockout policy

Network Level Authentication makes the client authenticate before the server builds a logon screen, which saves server resources and blocks older unauthenticated attacks. Check it on a host:

(Get-CimInstance -Namespace root\cimv2\TerminalServices -ClassName Win32_TSGeneralSetting -Filter "TerminalName='RDP-tcp'").UserAuthenticationRequired

1 means NLA is required. Enforce it with Group Policy as shown in Enable Remote Desktop with Group Policy.

Account lockout limits how many passwords an attacker can try per account. Microsoft’s reference for Account lockout threshold (Computer Configuration > Windows Settings > Security Settings > Account Policies > Account Lockout Policy) shows a default of 0 (never lock) in the default domain policy, and says Windows security baselines treat 10 as an acceptable starting point. Check the effective values on a server:

net accounts

Set the threshold, duration and reset counter in the Default Domain Policy (or a fine-grained password policy for admin groups; see PSO guide). Lockout is a trade-off: an attacker who knows real usernames can lock those users out on purpose. That is another reason to remove direct internet exposure rather than rely on lockout alone. Trace lockouts with event 4740.

Also rename or disable unused local accounts, make sure no account with RDP rights has a weak password, and limit the Remote Desktop Users group to people who need it.

Step 3: block attacking IPs automatically (optional)

For a server that has to keep a public RDP port for now, you can block addresses that fail too often. The snippet below reads the last hour of 4625 events (types 3 and 10), finds IPs over a threshold, and adds them to one inbound block rule. It is read-only by default: it only lists what it would block until you set $Apply = $true.

# Block-RdpBruteForce snippet - review before use. Read-only unless $Apply = $true.
$Apply     = $false
$Hours     = 1
$Threshold = 20
$RuleName  = 'Block RDP brute force'
$Allow     = @('203.0.113.10', '127.0.0.1', '::1', '-')    # your own IPs: never block these

$since = (Get-Date).AddHours(-$Hours)
$fails = Get-WinEvent -FilterHashtable @{ LogName = 'Security'; Id = 4625; StartTime = $since } -ErrorAction SilentlyContinue |
    ForEach-Object {
        $x = [xml]$_.ToXml()
        $d = @{}; $x.Event.EventData.Data | ForEach-Object { $d[$_.Name] = $_.'#text' }
        if ($d['LogonType'] -in '3', '10' -and $d['IpAddress'] -and $d['IpAddress'] -notin $Allow) { $d['IpAddress'] }
    }

$bad = $fails | Group-Object | Where-Object Count -ge $Threshold | Select-Object -ExpandProperty Name
if (-not $bad) { Write-Output 'Nothing over threshold.'; return }
Write-Output ("Over threshold: " + ($bad -join ', '))

if ($Apply) {
    $rule = Get-NetFirewallRule -DisplayName $RuleName -ErrorAction SilentlyContinue
    if ($rule) {
        $current = ($rule | Get-NetFirewallAddressFilter).RemoteAddress | Where-Object { $_ -ne 'Any' }
        $merged  = @($current) + @($bad) | Sort-Object -Unique
        Set-NetFirewallRule -DisplayName $RuleName -RemoteAddress $merged
    } else {
        New-NetFirewallRule -DisplayName $RuleName -Direction Inbound -Action Block -Protocol TCP -LocalPort 3389 -RemoteAddress $bad | Out-Null
    }
    Write-Output "Rule '$RuleName' updated."
}

Run it from a scheduled task every 10 to 15 minutes as SYSTEM once you are happy with what it reports. Things to keep in mind:

  • If RDP arrives through an RD Gateway or a load balancer, the source IP in 4625 is that device. Blocking it blocks everyone, so do not run this on hosts behind a gateway.
  • Block rules override allow rules in Windows Firewall, so this works even with the built-in Remote Desktop rules enabled.
  • The list only grows. Review it monthly and clear old entries (Remove-NetFirewallRule -DisplayName "Block RDP brute force" and let it rebuild).
  • Attackers rotate addresses, so expect this to reduce noise, not to stop a distributed spray. Steps 1 and 2 do that.

What not to do

  • Do not rely on moving RDP to another port. Internet scanners find non-standard ports quickly; it hides you from the laziest bots only.
  • Do not disable NLA to “fix” a client that cannot connect. Update the client instead.
  • Do not set lockout to a very low value (for example 3) on accounts exposed to the internet; attackers can use it to lock out your staff.
  • Do not block by country alone and call it done: attackers use cloud hosts in every region.
  • Do not leave a local Administrator with a shared password on internet-facing servers; use Windows LAPS.

Check that it worked

  • An external port scan (from outside your network) no longer shows TCP 3389 open, or shows it only from the allowed sources.
  • The count of 4625 events per hour drops sharply after you close or scope the port.
  • Legitimate users connect through the gateway or VPN, and their sign-ins trigger MFA.
  • If you use the blocking snippet: Get-NetFirewallRule -DisplayName "Block RDP brute force" | Get-NetFirewallAddressFilter lists the blocked IPs.

Official documentation: Event 4625: An account failed to log on · Account lockout threshold · New-NetFirewallRule · Set-NetFirewallRule

Related: RDP Connection Logs: 14 Event IDs to Track Who Connected and From Where · Enable Remote Desktop with Group Policy: NLA, Firewall Rules and User Access · Windows Firewall Group Policy: 5 Steps to Deploy Secure Rules · RDS Farm Windows Server 2025: Complete Deployment in 9 Steps · IP Blacklist Check: Bulk IPs, CIDR Ranges and Domains (33 Lists)

See also: Block RDP Brute Force on Windows Server: PowerShell Script

See also: Event ID 4625: An Account Failed to Log On (Status Codes)

Frequently asked questions

Why do failed RDP logons show logon type 3 instead of 10?

With Network Level Authentication, the password is checked during network authentication before a Remote Desktop session is created, so failures are often logged as type 3. Monitor both types.

Is changing the RDP port enough to stop brute force?

No. Scanners find non-standard ports quickly. Close the port to the internet and use RD Gateway or a VPN instead.

What account lockout threshold should I use?

Microsoft’s security baselines suggest 10 failed attempts as an acceptable starting point. Very low values let attackers lock out real users.

Can Windows block brute-force IPs automatically like fail2ban?

Not as a built-in feature for RDP. A scheduled PowerShell task that reads event 4625 and updates a firewall block rule, like the snippet in this guide, does the same job.

Do I need RD Gateway if I use a VPN?

Not necessarily. Either removes direct RDP exposure. RD Gateway adds per-user and per-host policies and works over HTTPS without a VPN client.

Free website test

Is your website set up right?

Check SSL, security headers, redirects, robots.txt, sitemap, llms.txt and security.txt in one test. It takes about 30 seconds.