如果你管过哪怕一台长期开机的 Windows 机器,那对 3389 这个端口绝对不会陌生。半年前我给自己一台 Windows Server 2022 例行巡检,翻安全日志时发现一个晚上居然有上千条 RDP 登录失败记录,全是扫描器在猜口令。把默认端口改掉之后,这类暴力探测噪音瞬间下降了两个数量级。以前改端口这事全靠手动折腾:改注册表、加防火墙规则、重启服务,每一步都得小心翼翼。后来我把整套配置过程封装成一个自带检查逻辑的批处理脚本,顺手整理出一套局域网直连 + 公网地址映射的双链路连接方案,放进我自己维护的 WorkBuddy 工具集里。用到现在大半年,稳得很。今天就把这套东西完整拆开讲清楚。IT 运维、搞自托管服务、经常在外需要连回家里电脑的折腾党,应该都能用上。
1. 为什么要改端口:默认 3389 就是公网上的活靶子
1.1 被扫描器盯上其实是常态
接入公网的 Windows 机器几乎每天都在被全网扫描器探测。它们的例行动作很简单:扫目标 IP 的 TCP 3389 端口,发现端口开着就往里面怼弱口令、试漏洞。我见过最夸张的记录是一台刚装好系统、还没配防火墙的机器,3 个小时内日志里出现二十多万条登录尝试。这不是个别现象,而是公网环境每天都在发生的事。
光靠 Windows 自带的账户锁定策略虽然能缓解一部分暴力破解,但问题在于:默认端口是公开的,扫描器不需要花任何心思就能锁定目标。大量无效登录请求本身就会占用系统资源、刷爆安全日志,而且一旦管理员用了弱密码,服务器基本等同于裸奔。
改端口本质上不是为了设置什么不可逾越的障碍,而是把默认的、可预测的入口变成一个需要扫描器额外费劲去探测的参数。全网扫描器大多只扫常见端口清单,你把它改成 13390 之后,自动化的扫描流量自然会大幅减少。当然,针对性攻击不在这个范畴内,后面第 4 章我会专门聊在公网场景下还需要做哪些事。
1.2 手动配置三步走,一步错就当场翻车
绝大多数情况下,改 RDP 端口需要动三样东西:注册表、防火墙、远程桌面服务。注册表路径要记对,防火墙要开对端口,服务要重启到位,三步全对才能连上。问题就出在——这三步里任何一步错了,结果往往是改完端口自己连不回去,人在办公室,机器在机房,只能跑现场。
我早期手动配置的流程大概是这样的:
- Win+R 打开 regedit,定位到
HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp,把PortNumber从 3389 改成目标端口。 - 打开 Windows Defender 防火墙,新建入站规则,放行 TCP 对应端口。
- 打开服务管理器,重启
Remote Desktop Services。
这套流程看起来很简单,实际执行时坑很多。比如注册表 DWORD 值你到底是输十进制还是十六进制?防火墙规则作用域是选域、专用还是公用?服务器装的是 Server Core,没有图形界面,连 regedit 都打不开怎么办?批量给几十台机器做同样的配置,总不能一台一台去点。
所以我才把这三个步骤写成了无脑执行的批处理脚本。脚本自己判断"注册表有没有写进去、防火墙规则建没建成、服务状态对不对",出问题能直接在控制台看到原因,不用再靠肉眼排查。
2. 脚本核心模块拆解:注册表、防火墙与服务联动
2.1 注册表端口写入:DWORD 值别搞混
RDP 监听端口存在注册表这个位置:
HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp键值名是PortNumber,数据类型是REG_DWORD,默认值为十进制 3389。写入用reg add命令:
reg add "HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp" /v PortNumber /t REG_DWORD /d 13390 /f这里面有两个容易被忽略的地方。第一,/t REG_DWORD后面跟的/d参数,直接写十进制数值就行,比如13390,不用换算成十六进制,reg add会自动转换。第二,/f参数表示强制覆盖,不加的话如果键值已存在会交互式询问是否覆盖,脚本跑起来就会卡住。
除了端口号,还需要确认远程桌面功能本身是开启的,对应键值是:
HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server\fDenyTSConnections这个值等于0,表示允许远程连接;等于1,表示拒绝。有些精简版系统装完默认是拒绝状态,图形界面的"允许远程桌面连接"关着,光改端口不把这个值改过来,端口配得再对也连不上。脚本里我会同时把这里纠偏。
2.2 防火墙规则创建:先删旧规则再建新规则
Windows 自带的防火墙管理命令是netsh advfirewall,新增规则写法如下:
netsh advfirewall firewall delete rule name="WorkBuddy_RDP" >nul 2>&1 netsh advfirewall firewall add rule name="WorkBuddy_RDP" dir=in action=allow protocol=TCP localport=13390 profile=any第一行很重要。批量跑脚本的时候,如果之前已经创建过同名规则,不先删除的话可能产生规则冲突、或者覆盖失败。我习惯用固定的规则名前缀,比如WorkBuddy_RDP,这样每次执行脚本都会先清理旧规则再建新规则,保证状态一致。
第二行参数说明:
dir=in:入站规则,针对外部连进来的流量。action=allow:允许通过。protocol=TCP:RDP 走的是 TCP。localport=13390:目标端口,也就是你刚在注册表里改好的那个端口。profile=any:同时作用在域、专用、公用三种网络类型上。如果你希望只在专用网络里生效,可以改成profile=private,domain,但在现实办公环境里,很多时候服务器所在的网络被 Windows 识别为"公用",保守起见用any最省心。
这里多说一句,如果机器上装了第三方安全软件,它可能会接管防火墙,netsh advfirewall添加的规则未必能同步到它的规则引擎里。这种情况我在第 4 章会单独讲。
2.3 服务重启与状态检查:验证环节不能省
注册表改完、防火墙规则建好,并不代表配置已经生效。TermService 进程在启动时读取的是默认 3389 端口,必须重启服务才能读取新的注册表值。
批处理里对应的命令:
net stop TermService net start TermService需要注意,Remote Desktop Services这个服务依赖Remote Desktop Services UserMode Port Redirector(UmRdpService)。有些系统版本只重启 TermService 会提示依赖服务未启动,稳妥的做法是先看依赖服务状态,必要的话一起处理。
这里要提醒一件事:如果你是通过远程桌面连着这台机器跑脚本,重启 TermService 会把当前会话直接断掉。我自己的习惯是:要么在服务器本地控制台执行,要么提前告诉同事"断开重连是正常现象"。脚本里也可以加一个提示,让操作的人有心理准备。
服务启动后,还需要验证端口是否真的在监听。命令:
netstat -ano | findstr :13390能看到 LISTENING 状态就说明服务读取新端口成功了。脚本里可以把这个结果直接打印出来,方便操作者当场判断。
3. 双链路方案:局域网直连与公网 NAT 映射
3.1 局域网直连:脚本顺手输出内网地址
远程桌面连接命令的格式是:
mstsc /v:IP地址:端口号比如内网机器 IP 是192.168.1.100,端口改成13390,那连接时就是:
mstsc /v:192.168.1.100:13390在脚本结束时自动获取并打印本机的 IPv4 地址会方便很多。批处理里可以调用ipconfig抓取,也可以用 PowerShell 只读取活动的 IPv4 地址:
for /f "tokens=14" %%a in ('ipconfig ^| findstr /i "IPv4"') do echo 局域网 IPv4:%%a这种方法在多网卡机器上会输出所有 IPv4 地址,正好把内网口、管理口的地址都列出来。用户直接复制一个地址去连就行,省得跑 cmd 敲 ipconfig 还要自己找。
局域网直连场景下,端口是不是常用端口其实无所谓,内网环境相对可控,重点是延迟低、带宽够。如果办公室里有域环境,建议关闭脚本里改端口的操作,只做防火墙规则检查,避免和域策略冲突。这个我在第 5 章会给出判断逻辑。
3.2 公网链路:路由器端口转发 + 非标准端口
有公网 IPv4 的情况下,想要从外部直接连到内网机器,需要在路由器或防火墙上做端口转发,也常被称为 NAT 映射。转发规则核心参数是四样:
- 协议:TCP
- 外部端口:自定义一个高位端口,比如 13390
- 内部 IP:内网那台 Windows 机器的地址,比如 192.168.1.100
- 内部端口:也就是脚本里设置的 RDP 监听端口,通常和外部端口保持一致
配置完路由器之后,公网侧的连接方式就是:
mstsc /v:公网IP:13390路由器收到这个端口的入站流量,会按照转发规则把它交给内网机器的 13390 端口,这样 RDP 连接就能穿透到内网。
很多家用路由器管理界面里"转发规则"或"虚拟服务器"的位置就能配置。需要特别注意的是,如果运营商分配给你的不是静态公网 IP,而是动态公网 IP,那 IP 每隔一段时间会变。这种情况可以配合 DDNS 服务,把动态 IP 绑定到一个固定域名上,外网连接直接写mstsc /v:yourdomain.example:13390即可。DDNS 的解决办法在很多路由器里是内置功能,填上服务商提供的账号就行,不用在 Windows 里额外装东西。
3.3 双链路使用技巧:内外网用不同的连接入口
所谓双链路直连,本质就是结合局域网和公网两种场景:
| 场景 | 连接目标 | 典型入口 |
|---|---|---|
| 办公室 / 家里局域网 | 内网 IP:13390 | mstsc /v:192.168.1.100:13390 |
| 出差 / 外网 | 公网 IP 或 DDNS 域名:13390 | mstsc /v:yourdomain.example:13390 |
在局域网里连接,速度快稳定性好;在外面用公网域名连接,只要有网就能回到机器上。两条链路共用同一个 RDP 监听端口,不需要额外配置。
公网映射场景下,我还建议你做两个加固动作:
- 在路由器上限制允许访问的源 IP。很多路由器支持在端口转发规则里限定来源 IP,只放行你公司出口 IP 或家里宽带的 IP,其余全部拒绝。这样哪怕端口暴露在公网上,绝大多数扫描流量也会被挡在路由器外面。
- 在 Windows 里启用 NLA(网络级别认证)。让客户端在登录窗口弹出之前就先完成一层凭据校验,不改协议端口也能减少很多无效连接。
这两个动作用不上也行,但做了之后,远程桌面的安全体验会有明显提升。
4. 部署过程中的坑:我踩过的那些典型问题
4.1 改完端口后连不上:先怀疑服务没刷新
脚本改完端口、建好防火墙规则,看着每一步都正常,结果在另一台机器上mstsc /v:IP:13390还是报"无法连接"。这种场景我遇到太多次了,排错顺序非常重要。
第一步看端口监听:
netstat -ano | findstr :13390如果这里什么都没显示,说明 TermService 没有读到新端口,按经验大概率是服务没重启成功,或者重启后又因为某种原因退出了。再去看服务状态:
sc query TermService如果服务处于STOPPED,手动启动一次并观察是否报错。这里容易出现一个怪现象:服务启动后用 netstat 看到的是 3389 而不是 13390。原因很可能是服务没有真正重启,只是把它停止后又在旧的注册表缓存基础上恢复了。最粗暴但有效的办法是重启整台服务器。
第二步检查防火墙规则:
netsh advfirewall firewall show rule name="WorkBuddy_RDP"确认规则存在、方向是 In、动作是 Allow、协议 TCP、端口写的是你新改的端口。注意有没有手滑把端口写错。
第三步在服务器本机做回环测试:
telnet 127.0.0.1 13390本机能通,说明服务和防火墙规则没问题,问题出在中间网络环境;本机都不能通,那就是前面几步配置的问题。
4.2 第三方安全软件接管防火墙:注册表被回滚
现在很多 Windows 服务器上装的是第三方安全软件,这类软件通常会接管系统防火墙,甚至监控注册表关键项的变更。有次我在一台装了三六零安全卫士的机器上跑脚本,日志里显示注册表写入成功,防火墙规则添加成功,但实测端口怎么都不通。最后发现是安全软件把注册表的PortNumber又改回了 3389,防火墙规则也被它的主动防御拦掉了。
处理办法分两种。一种是在安全软件里把批处理脚本加入白名单,再跑一遍;另一种是干脆用安全软件自己提供的图形界面或命令行工具来加放行规则。我的经验是:跑脚本前先跟安全软件打个招呼,先把拦截弹窗那个界面开着,脚本每执行一步都看一眼有没有被拦。跑完再验证整条链路的注册表值、防火墙规则、端口监听,一次通过才算数。
4.3 改端口挡不住所有爆破:账户策略要跟上
改了端口之后,全网扫描器的噪音会明显下降,但如果有人针对你的公网 IP 做定向扫描,高位端口照样能被发现。因为 TCP 端口一共就 65535 个,要扫一个特定 IP 的所有端口,在流量不大的情况下,几分钟内也能得到结果。
所以我一直强调,改端口只是第一层过滤,真正扛爆破的还是账户策略。建议打开本地安全策略,做三件事:
- 设置账户锁定阈值为 5 次:连续输错 5 次密码,账户锁定 15 分钟或者更久。
- 禁用内置 Administrator 账户,新建一个普通管理员账户,用长密码。
- 启用 NLA(网络级别认证),不能在 login 阶段直接暴露桌面。
我用net accounts命令行来设置锁定策略,因为批处理脚本里调用最方便:
net accounts /lockoutthreshold:5 net accounts /lockoutduration:15 net accounts /lockoutwindow:15在域环境里这套策略会被域控制器下发的组策略覆盖,但在工作组环境或独立服务器上,命令行设置完立刻生效。
4.4 兼容性差异:Server Core、Win7、老版本 netsh
不同版本 Windows 的网络命令存在细微差异。Windows Server Core 没有图形界面,批处理脚本反而更适合它,因为它天然就工作在命令行环境。但 Windows 7 和 Server 2008 R2 上跑netsh advfirewall时,profile=any这个参数是支持的,不用担心;真正需要注意的反而是如果你在这个环境上把服务所在配置文件指定成private,domain,而实际网络被识别为公用网络,规则就不会命中。
另外,如果你之前在这台机器上用过 RDP Wrapper 这类工具来突破多用户并发限制,配置 RDP 端口时就要格外小心。RDP Wrapper 的原理是劫持 termsrv.dll,它会接管一部分注册表配置。我和它同时改PortNumber时会互相覆盖,表现为端口改完过一阵子又变回默认值,或者 RDP Wrapper 显示not supported。在同一环境里,建议二选一:要么用 RDP Wrapper 的完整方案,要么用系统自带的 RDP 配置加注册表改端口,混用通常没好下场。
5. WorkBuddy 完整代码:带参数的一键脚本与回滚方案
5.1 核心脚本:WorkBuddy_RDP_SetPort.bat
我把脚本写成支持带参执行,默认端口 13390,也可以用参数指定其他端口。脚本内容包括权限检查、参数解析、注册表写入、防火墙规则重建、服务重启、结果验证,最后打印本机 IPv4 和连接方式。
@echo off chcp 65001 >nul setlocal enabledelayedexpansion :: ============================================ :: WorkBuddy - RDP Port & Firewall Config :: 用法: WorkBuddy_RDP_SetPort.bat [端口号] :: 示例: WorkBuddy_RDP_SetPort.bat 13390 :: ============================================ :: ---- 1. 管理员权限检查 ---- net session >nul 2>&1 if %errorlevel% neq 0 ( echo [错误] 请右键选择"以管理员身份运行"本脚本。 pause exit /b 1 ) :: ---- 2. 端口参数解析 ---- set "RDP_PORT=13390" if not "%~1"=="" set "RDP_PORT=%~1" :: 简单校验端口范围 if %RDP_PORT% lss 1024 ( echo [警告] 端口低于1024不建议用于RDP,继续执行可能导致意外问题。 choice /c YN /m "是否继续" if errorlevel 2 exit /b 1 ) if %RDP_PORT% gtr 65535 ( echo [错误] 端口号超过65535,不合法。 pause exit /b 1 ) echo [信息] 目标RDP端口: %RDP_PORT% :: ---- 3. 注册表写入 ---- reg add "HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp" /v PortNumber /t REG_DWORD /d %RDP_PORT% /f >nul if errorlevel 1 ( echo [错误] 注册表端口写入失败,请检查权限或安全软件拦截。 pause exit /b 1 ) reg add "HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server" /v fDenyTSConnections /t REG_DWORD /d 0 /f >nul :: ---- 4. 防火墙规则重建 ---- netsh advfirewall firewall delete rule name="WorkBuddy_RDP" >nul 2>&1 netsh advfirewall firewall add rule name="WorkBuddy_RDP" dir=in action=allow protocol=TCP localport=%RDP_PORT% profile=any >nul if errorlevel 1 ( echo [错误] 防火墙规则创建失败。 pause exit /b 1 ) echo [信息] 防火墙规则已放行 TCP %RDP_PORT% :: ---- 5. 重启远程桌面服务 ---- echo [信息] 正在重启 Remote Desktop Services ... net stop TermService >nul 2>&1 timeout /t 2 /nobreak >nul net start TermService >nul 2>&1 :: 等几秒让服务稳定 ping -n 5 127.0.0.1 >nul :: ---- 6. 验证端口监听 ---- netstat -ano | findstr ":%RDP_PORT%" | findstr "LISTENING" >nul if errorlevel 1 ( echo [警告] 端口 %RDP_PORT% 未检测到监听状态。 echo [提示] 请尝试重启本机后再用 netstat 检查。 ) else ( echo [确认] 端口 %RDP_PORT% 已进入监听状态。 ) :: ---- 7. 输出连接信息 ---- echo. echo ======================================== echo 本机 IPv4 地址: for /f "tokens=14" %%a in ('ipconfig ^| findstr /i "IPv4"') do ( echo %%a ) echo. echo 局域网连接: mstsc /v:本机IPv4地址:%RDP_PORT% echo 公网连接: mstsc /v:公网IP或域名:%RDP_PORT% echo 需要先在路由器做 TCP %RDP_PORT% 端口转发 echo ======================================== echo. pause脚本里net stop TermService之后我加了 2 秒等待,这样服务停止得干净一点,重启后不容易出现端口被 TIME_WAIT 状态占住的奇怪问题。ping -n 5 127.0.0.1纯粹是当作 sleep 用,Windows 批处理没有 Unix 那种sleep 3,用这个办法最省事。
chcp 65001配合脚本内的中文提示时,建议将 bat 文件保存为UTF-8 编码,否则中文可能显示乱码。如果你懒得改编码,直接删掉第一行的chcp 65001 >nul,把文件存成 ANSI(GBK)编码,中文提示也能正常显示。两种做法都没问题,关键是 bat 文件的实际编码要和 CMD 的代码页匹配,这一条很多新手容易忽略。
5.2 回滚方案:WorkBuddy_RDP_Reset.bat
改端口这种事,配错了要能快速退回默认状态。回滚脚本逻辑很简单:把注册表端口重新写回 3389,删除自定义防火墙规则,重启服务。
@echo off chcp 65001 >nul setlocal enabledelayedexpansion net session >nul 2>&1 if %errorlevel% neq 0 ( echo [错误] 请以管理员身份运行。 pause exit /b 1 ) echo [信息] 恢复默认RDP端口 3389 ... reg add "HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp" /v PortNumber /t REG_DWORD /d 3389 /f >nul reg add "HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server" /v fDenyTSConnections /t REG_DWORD /d 0 /f >nul echo [信息] 删除 WorkBuddy_RDP 防火墙规则 ... netsh advfirewall firewall delete rule name="WorkBuddy_RDP" >nul 2>&1 echo [信息] 重启远程桌面服务 ... net stop TermService >nul 2>&1 timeout /t 2 /nobreak >nul net start TermService >nul 2>&1 netstat -ano | findstr ":3389" | findstr "LISTENING" >nul if errorlevel 1 ( echo [警告] 3389 未处于监听状态,建议重启系统。 ) else ( echo [确认] 3389 已恢复监听,可以继续使用 mstsc /v:IP:3389 连接。 ) pause回滚脚本同样需要管理员权限运行。如果你改完端口以后一直用的是mstsc /v:IP:13390,回滚到 3389 之后,客户端连接命令也要改回来,不然还是会连不上。因为 RDP 客户端使用mstsc /v:IP不写端口时,默认走的就是 3389。
5.3 批量部署与日常运维建议
WorkBuddy 脚本设计成命令行带参执行,天然适合批量部署。比如你有 20 台 Windows 服务器需要统一改成 13390 端口,可以使用远程执行方式(比如 PsExec 或其他运维工具)把脚本推上去跑。但批量场景里有一个和单机不同的坑:每台机器上的第三方安全软件环境可能不一样,有的放行注册表修改,有的会拦截。我的做法是先挑一台环境最接近的测试机执行一遍,确认脚本流程没问题,再批量铺开。
日常运维我还会配合计划任务做定期"体检":
- 用
reg query读取当前PortNumber,确认没有被安全软件回滚。 - 用
netsh advfirewall firewall show rule name="WorkBuddy_RDP"确认规则还在。 - 用
netstat -ano | findstr :端口确认 RDP 端口在监听。
这三个检查项可以写成一个很小的脚本,用计划任务每天跑一次,发现异常就写入日志,甚至通过邮件或其他告警通道发出来。
最后总结一点自己的使用习惯。我始终把"跑完脚本必须当场验证"作为铁律:脚本执行完毕后,一定要用另一台机器mstsc连一次,能正常进到登录界面才算配置完成。如果连不上,优先看netstat端口监听情况和防火墙规则状态,不要一上来就怀疑注册表写错了。WorkBuddy 这套脚本我从 Server 2008 R2 一路用到 Windows 11,核心逻辑基本没变过,真正变来变去的只有各种不同安全软件带来的环境兼容问题。按上面这套方式处理,再配上账户锁定策略和 NLA,远程桌面的公网直连其实没有那么吓人。