Secure RDP: keep Remote Desktop off the open internet
Contents
For the administrator of a small company whose accountants work on a server over Remote Desktop. After these steps port 3389 is closed to outsiders, accounts lock after several failed sign-ins, and the attempts show up in the log.
Why an open port 3389 is a problem#
Scanners find a server with a public address quickly, and password guessing against RDP runs around the clock. Even with strong passwords that means thousands of log entries, locked-out accountants and the risk that a new protocol vulnerability is exploited before you install the update. The main rule: port 3389 must not be open to the whole internet; every other setting is a second line of defence, not a substitute for the first. The DDoS protection included with each of our servers does not help here: it filters network-level attacks (L3/L4), while password guessing looks like ordinary connections (details).
What you need#
- Windows Server 2025 or 2022 and an account with administrator rights. All commands are for Windows PowerShell run as administrator.
- The list of addresses your users work from: the static public IP address of the office or the VPN subnet. If the addresses are dynamic (home or mobile internet), restricting by address will not work — you need a VPN.
- A fallback route to the server in case a rule goes wrong — the remote console (on request through support; not available on the Lite line). Arrange it before you touch the firewall.
- A fresh backup of the accounting databases.
If the server has just been delivered, first go through the first-hour steps for Windows Server.
Step 1 Close port 3389 to the internet#
The options run from most to least reliable.
Option A. RDP only through a VPN#
The most reliable setup: the server accepts RDP only from tunnel addresses, and only the VPN port is open to the outside. WireGuard does not answer packets without a valid key, so a scanner does not see it. Setting up the tunnel is covered in WireGuard: a protected channel between the office and the server. Afterwards leave only the VPN subnet in the firewall rule (option B), for example 10.66.0.0/24.
Option B. Allow RDP only from your addresses#
The built-in RDP rules belong to the “Remote Desktop” group. On localised systems the group name is translated, so it is safer to refer to the rules by their internal names: RemoteDesktop-UserMode-In-TCP, RemoteDesktop-UserMode-In-UDP, RemoteDesktop-Shadow-In-TCP. Check which addresses the rules allow now and which address you are connected from:
Get-NetFirewallRule -Name 'RemoteDesktop-*' | Get-NetFirewallAddressFilter | Format-Table InstanceID, RemoteAddress
Get-NetTCPConnection -LocalPort 3389 -State Established | Format-Table RemoteAddress, RemotePortThe first command should list these rules (with the address Any on a freshly installed system); if the list is empty, find the rule by port (command below).
Warning. A mistake in the address cuts you off from the server, usually together with your current session. Before the change, make sure your address from the second command is on the allowed list, and create a safety task: after 15 minutes it opens the rules to all addresses again (
Any). If access is lost and there is no safety task, the rule is fixed through the remote console with the same command and-RemoteAddress Any.
$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument '-NoProfile -Command "Set-NetFirewallRule -Name RemoteDesktop-* -RemoteAddress Any"'
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(15)
Register-ScheduledTask -TaskName 'RDP-rollback' -Action $action -Trigger $trigger -User 'NT AUTHORITY\SYSTEM' -RunLevel Highest
Set-NetFirewallRule -Name 'RemoteDesktop-*' -RemoteAddress '203.0.113.10', '198.51.100.0/24'Here 203.0.113.10 is an example office address and 198.51.100.0/24 an example subnet; use your own (value formats are in the Set-NetFirewallRule reference). Without closing the current session, open another connection from an allowed address. If it works, remove the safety task — otherwise the port opens to everyone again in 15 minutes:
Unregister-ScheduledTask -TaskName 'RDP-rollback' -Confirm:$falseAlso check for other allow rules on the same port: they work independently of the built-in ones.
Get-NetFirewallPortFilter -Protocol TCP | Where-Object { $_.LocalPort -eq '3389' } | Get-NetFirewallRule | Format-Table Name, DisplayName, Enabled, ActionRemote Desktop Gateway#
The third route is the Remote Desktop Gateway role: clients connect over HTTPS (port 443) and RDP itself is not open to the outside. It is a separate role with its own certificate and policies; for a handful of accountants a VPN is usually simpler.
Changing the port is not protection#
Moving RDP from 3389 to another port only briefly reduces log noise: scanners try every port and recognise RDP by its reply.
Step 2 Mandatory settings on the server#
Network Level Authentication (NLA)#
With NLA the server checks the user name and password before a session is created; without it anyone gets to the sign-in screen. Check it:
Get-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' -Name UserAuthenticationA value of 1 means NLA is required. If it is 0, turn it on:
Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' -Name UserAuthentication -Value 1The same is set by the “Require user authentication for remote connections by using Network Level Authentication” policy: gpedit.msc → Computer Configuration → Administrative Templates → Windows Components → Remote Desktop Services → Remote Desktop Session Host → Security. The policy takes precedence over the registry value.
Separate users and the built-in Administrator#
Each accountant works under their own account without administrator rights; the right to sign in over RDP comes from the Remote Desktop Users group. Groups are given by SID so the commands work in any localisation: S-1-5-32-555 is Remote Desktop Users, S-1-5-32-544 is Administrators.
$pw = Read-Host -AsSecureString -Prompt 'Password'
New-LocalUser -Name 'accountant1' -Password $pw -FullName 'Accountant 1'
Add-LocalGroupMember -SID 'S-1-5-32-555' -Member 'accountant1'Attackers try the name Administrator first, so create another administrator. The name admin here is an example; pick one that is not in guessing dictionaries.
$pw = Read-Host -AsSecureString -Prompt 'Password'
New-LocalUser -Name 'admin' -Password $pw
Add-LocalGroupMember -SID 'S-1-5-32-544' -Member 'admin'Sign in as the new administrator in a separate session and only then disable the built-in account (its SID ends in 500):
Get-LocalUser | Where-Object { $_.SID -like 'S-1-5-21-*-500' } | Disable-LocalUserInstead of disabling the account you can rename it with Rename-LocalUser; Enable-LocalUser turns it back on. If scheduled tasks or backups run under the built-in account, move them to another account first.
Account lockout and passwords#
net accounts shows the current values. Per Microsoft article KB5020282, on systems installed from an image that already includes the 11 October 2022 update (Windows Server 2025 and recent Windows Server 2022 images), lockout is on from the start: 10 failed attempts within 10 minutes lock the account for 10 minutes, the built-in Administrator included. On systems installed from older images the threshold is 0, meaning no lockout. Check, and set the values if needed:
net accounts
net accounts /lockoutthreshold:10 /lockoutwindow:15 /lockoutduration:15
net accounts /minpwlen:12The same settings are in secpol.msc → Account Policies → Account Lockout Policy; while there, check that “Allow Administrator account lockout” is enabled and, under Password Policy, “Password must meet complexity requirements”. Lockout of the built-in administrator affects network sign-in only; signing in from the console still works. If the server is in a domain, the domain sets these policies. Password length matters more than special characters: a phrase of several words beats a short “complex” word.
Updates, session limits and redirection#
- Updates. Install the monthly Windows updates (Settings → Windows Update or
sconfig) and plan time for the restart: these are what close RDP vulnerabilities. - Session limits. In
gpedit.msc, next to the NLA policy, there is the Session Time Limits section. “Set time limit for active but idle Remote Desktop Services sessions” disconnects an idle session (say, after 30–60 minutes); “Set time limit for disconnected sessions” ends a disconnected one (say, after a few hours). Ending a session closes programs without saving, so agree the values with the accountants. - Redirection. The Device and Resource Redirection section has “Do not allow drive redirection” and “Do not allow Clipboard redirection”. Drives of a home computer mapped into the session are a path for malware onto the server and for data off it. Decide deliberately: if nobody transfers files over RDP, turn drive redirection off; keep the clipboard only if work needs it.
After changing policies run gpupdate /force; they apply to new connections.
How to check the result#
- From a network that is not on the allowed list (mobile internet, for example) run
Test-NetConnection -ComputerName 203.0.113.50 -Port 3389(the address is an example). You should getTcpTestSucceeded : False, andTruefrom the office or through the VPN. net accountsshows a non-zero lockout threshold, the NLA check returns1, andGet-LocalUsershows the built-in Administrator as disabled.- The log no longer contains sign-in attempts by strangers. A failed sign-in is event 4625 in the Security log:
$events = @(Get-WinEvent -FilterHashtable @{ LogName = 'Security'; Id = 4625; StartTime = (Get-Date).AddDays(-1) } -ErrorAction SilentlyContinue)
$events.Count
$events | Group-Object { $_.Properties[19].Value } | Sort-Object Count -Descending | Select-Object -First 10 Count, Name
$events | Group-Object { $_.Properties[5].Value } | Sort-Object Count -Descending | Select-Object -First 10 Count, NameThe first table lists source addresses, the second the user names that were tried. Hundreds or thousands of entries a day from unknown addresses mean the port is still open; after step 1 only your own users’ mistakes should remain. If there are no events at all, check the auditing of failed sign-ins and enable it if needed:
auditpol /get /subcategory:"{0CCE9215-69AE-11D9-BED3-505054503030}"
auditpol /set /subcategory:"{0CCE9215-69AE-11D9-BED3-505054503030}" /failure:enableCommon mistakes#
- The rule is restricted by address but the office has a dynamic IP address: as soon as it changes, nobody can get in. Dynamic addresses need a VPN.
- The built-in rules were changed, but another allow rule for port 3389 is still there, or Windows Firewall is turned off “while we set things up”. Do not turn it off.
- Lockout was enabled but the port left open: an outsider who knows the user names locks the accountants out on purpose. Do step 1 first.
- All accountants share one account or have administrator rights “so the program can update”.
- NLA was turned off because an old client could not connect. Update the client instead of weakening the server.
- Password expiry was forgotten: the default is 42 days (Maximum password age in
net accounts), and with NLA an expired password cannot be changed at sign-in. Remind users to change their password in advance (Ctrl+Alt+End in the session).
What next#
- A compromised RDP most often ends with encrypted databases, so keep copies away from the server: Windows Server backups and the 3-2-1 rule. On the main lines a server comes with backup space on separate storage; you set up the copying yourself.
- Choosing a server for accountants on RDP? See a server for BAS for 5, 10, 20 and 50 users and the dedicated server catalogue.
- Need the remote console, or lost access after changing the rules? Technical support is available 24/7: +38 044 206 08 08, info@united.net.ua.