· 6 min read

RDP Security on Windows Server VDS: Lockout, NLA, Firewall

RDP security on a Windows Server VDS comes down to four controls: limit who can reach the port, force Network Level Authentication (NLA), lock accounts after failed attempts, and automatically block IPs that keep guessing passwords. Changing the port helps with log noise, but it is not a security control on its own.

A VDS with a public IPv4 address gets RDP scans within minutes of being provisioned. This guide covers the controls in order of impact, with commands you can run in an elevated PowerShell session. It assumes Windows Server 2016 or newer.

Before you start: keep your current RDP session open and make sure you have out-of-band console access to the VDS. A wrong firewall rule can lock you out, and the console is your way back in.

1. Restrict the firewall scope first

The most effective step is making RDP unreachable from the internet at large. If only your office or admin VPN needs access, say so in the firewall.

List the built-in RDP rules:

Get-NetFirewallRule -DisplayGroup "Remote Desktop" |
  Select DisplayName, Enabled, Direction, Action

Scope them to trusted addresses (example uses documentation IPs):

Get-NetFirewallRule -DisplayGroup "Remote Desktop" |
  Set-NetFirewallRule -RemoteAddress 203.0.113.10,198.51.100.0/28

If your admin IP changes often, don't open RDP to "Any". Put a VPN in front (WireGuard or OpenVPN on a small separate host, or a VPN service) and allow RDP only from the VPN exit address or the VPN subnet.

Also check that no other rule allows the same port. Windows Firewall is additive, so one forgotten "Allow Any" rule defeats your scoped one:

Get-NetFirewallPortFilter | Where-Object { $_.LocalPort -eq 3389 } |
  Get-NetFirewallRule | Select DisplayName, Enabled, Action

2. Change the RDP port (for noise reduction)

Moving RDP off TCP 3389 cuts down automated scans in your logs. A targeted scan finds the new port quickly, so treat this as a supplement to step 1.

Create the firewall rules first, then change the port:

$port = 33890
New-NetFirewallRule -DisplayName "RDP custom TCP" -Direction Inbound `
  -Protocol TCP -LocalPort $port -Action Allow `
  -RemoteAddress 203.0.113.10
New-NetFirewallRule -DisplayName "RDP custom UDP" -Direction Inbound `
  -Protocol UDP -LocalPort $port -Action Allow `
  -RemoteAddress 203.0.113.10

Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' `
  -Name PortNumber -Value $port

Restart-Service TermService -Force

Open a second RDP session to 192.0.2.50:33890 and confirm it works before you disable the old rules (Disable-NetFirewallRule -DisplayGroup "Remote Desktop"). Restarting TermService will drop your current session, so have the console ready.

3. Enforce NLA and modern encryption

NLA makes the client authenticate before the server builds a full desktop session. That reduces resource use from unauthenticated connections and blocks old attack paths that relied on pre-auth sessions.

$p = 'HKLM:\System\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp'
Set-ItemProperty -Path $p -Name UserAuthentication -Value 1
Set-ItemProperty -Path $p -Name SecurityLayer -Value 2        # TLS
Set-ItemProperty -Path $p -Name MinEncryptionLevel -Value 3   # High

Verify:

Get-CimInstance -Namespace root\cimv2\TerminalServices `
  -ClassName Win32_TSGeneralSetting |
  Select TerminalName, UserAuthenticationRequired, SecurityLayer

UserAuthenticationRequired should be 1. Keep the OS patched too. Wormable RDP flaws such as BlueKeep (CVE-2019-0708) were serious regardless of password strength.

4. Fix accounts: names, groups, and passwords

Attackers try Administrator first. A few small changes remove most of the easy wins:

Get-LocalGroupMember "Remote Desktop Users"
Get-LocalGroupMember "Administrators"

5. Set an account lockout policy

Lockout slows down password guessing against any account that is reachable.

net accounts /lockoutthreshold:5 /lockoutduration:15 /lockoutwindow:15
net accounts

This locks an account for 15 minutes after 5 failures within a 15-minute window. On a domain-joined server, set it in Group Policy instead (Computer Configuration > Windows Settings > Security Settings > Account Policies > Account Lockout Policy).

One trade-off: an attacker spraying your known username from the internet can keep a legitimate account locked out. This is another reason to scope the firewall in step 1 and block offending IPs in step 6.

6. Block brute-force IPs automatically

Lockout protects accounts. Blocking the source IP protects the server. First make sure failed logons are logged:

auditpol /set /subcategory:"Logon" /success:enable /failure:enable

Failed RDP logons appear in the Security log as Event ID 4625. With NLA enabled, they usually show Logon Type 3; without it, Type 10. Check what you see:

Get-WinEvent -FilterHashtable @{LogName='Security';Id=4625} -MaxEvents 20 |
  ForEach-Object {
    [pscustomobject]@{
      Time = $_.TimeCreated
      User = $_.Properties[5].Value
      IP   = $_.Properties[19].Value
      Type = $_.Properties[10].Value
    }
  }

Then use a scheduled script that blocks IPs crossing a threshold:

# block-rdp-bruteforce.ps1
$threshold = 5
$window    = (Get-Date).AddMinutes(-10)
$allow     = @('203.0.113.10')   # never block your own addresses

$events = Get-WinEvent -FilterHashtable @{
  LogName='Security'; Id=4625; StartTime=$window } -ErrorAction SilentlyContinue

$bad = $events | ForEach-Object { $_.Properties[19].Value } |
  Where-Object { $_ -match '^\d{1,3}(\.\d{1,3}){3}$' -and $_ -notin $allow } |
  Group-Object | Where-Object Count -ge $threshold

foreach ($b in $bad) {
  $name = "AutoBlock $($b.Name)"
  if (-not (Get-NetFirewallRule -DisplayName $name -ErrorAction SilentlyContinue)) {
    New-NetFirewallRule -DisplayName $name -Direction Inbound `
      -RemoteAddress $b.Name -Action Block | Out-Null
  }
}

Run it every few minutes:

$a = New-ScheduledTaskAction -Execute 'powershell.exe' `
  -Argument '-NoProfile -ExecutionPolicy Bypass -File C:\Scripts\block-rdp-bruteforce.ps1'
$t = New-ScheduledTaskTrigger -Once -At (Get-Date) `
  -RepetitionInterval (New-TimeSpan -Minutes 5)
Register-ScheduledTask -TaskName "RDP AutoBlock" -Action $a -Trigger $t `
  -User "SYSTEM" -RunLevel Highest

Block rules accumulate, so clean up old ones periodically. Maintained tools such as IPBan do the same job with expiry built in; evaluate any third-party tool before running it as SYSTEM.

7. Reduce what a successful login can do

8. Verify from the outside

Test from a machine that is not on your allow list:

Test-NetConnection 192.0.2.50 -Port 3389
Test-NetConnection 192.0.2.50 -Port 33890

Both should fail. Then test from an allowed address and confirm you can connect. On the server, check what is listening:

Get-NetTCPConnection -State Listen | Where-Object LocalPort -in 3389,33890

Common mistakes and fixes

Mistake Fix
Changed the port before creating the firewall rule Use the provider console, add the rule, restart TermService
Old "Remote Desktop" rules still allow Any Disable or scope the whole display group
Lockout threshold of 1–3 Use 5+ to avoid locking out real users on typos
Auto-block script blocks your own IP Maintain the $allow list
No 4625 events appear Enable Logon auditing and check the log's size and retention
Only the port was changed Combine with scoping, NLA, and lockout

Network-level protection

Host controls don't help when the problem is volume. A flood of connection attempts can saturate the VDS uplink before Windows Firewall sees anything. For public-facing Windows servers, filtering upstream is a sensible complement. Nixcon operates its own network (AS40368), and its DDoS protection and firewall options filter traffic before it reaches your server. You can run Windows Server on a VDS with a dedicated IPv4 address and apply the steps above from day one.

Checklist

  1. RDP allowed only from trusted IPs or a VPN.
  2. Non-default port (optional) with matching firewall rules.
  3. NLA on, TLS security layer, high encryption.
  4. Renamed admin account, named users, strong passphrases.
  5. Lockout at 5 attempts, auditing enabled.
  6. Scheduled auto-block for repeat offenders.
  7. Updates current, tested from outside.

Frequently asked questions

Is changing the RDP port enough to secure a Windows Server VDS?

No. A non-default port reduces automated scan noise, but a targeted port scan finds it quickly. Combine it with firewall IP restrictions, NLA, account lockout, and automatic blocking of repeat offenders.

Should I keep NLA enabled?

Yes, in almost all cases. NLA requires authentication before a full session is created, which lowers resource use from unauthenticated connections. Only very old clients that don't support it are a reason to reconsider, and upgrading the client is the better fix.

Which event ID shows failed RDP logins?

Event ID 4625 in the Security log. Enable Logon auditing with auditpol first. With NLA, the Logon Type is usually 3; without NLA it is typically 10. The source IP is in the event's Network Information fields.

What if my admin IP changes and I get locked out by the firewall?

Use your provider's console to access the VDS and fix the rule. To avoid this in future, connect through a VPN with a fixed exit IP and allow RDP only from that address.

Run Windows Server on a BGP-capable VDS with network-level DDoS filtering.Learn more