SSTP 占用了 443 端口:Apache 为什么起不来
出问题的网站并不是占用 443 的那个。Windows SSTP 通过 HTTP 栈预留了端口:要么挪走监听器,要么在前面放 IIS,要么把站点搬走。
Apache 在有人开启传入 VPN 连接的那个下午停止在 443 上应答,而它的错误日志把责任推给 bind,而不是它背后的预留。Windows Routing and Remote Access 就在那一刻开启 SSTP,SSTP 监听器在任何客户端完成认证之前就通过 HTTP 栈占用了 TCP 443。即使证书缺失、名称不匹配或每个 handshake 都被拒绝,这个占用依然成立,所以一个谁都登不进去的 VPN 仍然占着端口。先读监听表;持有 443 的 PID 决定下面哪种修法对你才是可行的。
预留比坏掉的证书活得更久
SSTP 把 PPP 包在 TLS 会话里,所以它需要 443,所以它会穿过那些丢弃其他隧道的代理。监听器不是 VPN 服务为自己打开的套接字:SstpSvc 把它注册到内核的 HTTP 驱动上,因此只要服务在运行,端口就一直处于预留状态。那次注册从不查看证书,这意味着名称过期、subject 不匹配,或者根本没有证书,443 都一样被占着。故障随后分成两个方向:被拒绝的 handshake 落到 RasSstp 名下的 System 日志里,而 Web 服务器只能报告自己无法绑定。
netstat -aon | findstr :443
tasklist /svc /fi "PID eq <pid>"
第一行返回持有端口的 PID;第二行说出它背后的服务,答案通常是 HTTP 服务或 RRAS 栈。当 tasklist 以共享宿主进程作答时,请看服务那一列,而不是从进程名去猜。
RasSstp 的警告只有在这之后才值得读,因为它们描述的是失败的那次 handshake,对造成冲突的那次预留只字不提。一个拒绝所有客户端的 VPN 端点照样占着端口。把日志当作第二个问题,而不是第一个。
IIS 共享驱动,Apache 自己开套接字
IIS 把自己的绑定交给 SSTP 所用的同一个内核驱动,由该驱动按主机名和 URL 前缀来分流进来的连接,所以两个服务都可以被要求想要 443,而谁都不会失败。Apache 和 nginx 恰恰相反:它们在自己的套接字上调用 bind,操作系统因为地址已被占用而拒绝。两种配置本身都没有错;差别在于 哪一个 API 持有这个端口。这也是为什么开启 VPN 会让一台主机上的站点倒下,而另一台主机上完全相同的站点却毫发无损。
这种不对称决定了修复的形态。如果 IIS 已经是前门,冲突就变成前缀、绑定和证书的问题。如果前门是 Apache,要么它让出 443,要么由 SSTP 监听器让出,下一节覆盖这两种方案。
选择由哪个服务保留 443
监听端口存放在一个注册表值里,即 Sstpsvc 参数键下的十进制 DWORD。改掉这个值,重启 Routing and Remote Access,再从监听表确认旧端口已经空出来。
reg query "HKLM\SYSTEM\CurrentControlSet\Services\Sstpsvc\Parameters" /v ListenerPort
reg add "HKLM\SYSTEM\CurrentControlSet\Services\Sstpsvc\Parameters" /v ListenerPort /t REG_DWORD /d 4443 /f
Restart-Service RemoteAccess
挑一个主机上没人要的端口,用十进制输入:reg add 会按写入的样子原样保存 DWORD,所以一个本想写成十六进制的值会落到另一个端口上。如果查询说该值不存在,第二行就是用来创建它的。Restart-Service RemoteAccess 会断开所有已连接的人,值得安排时间。之后要重新跑一遍监听检查,因为 443 上残留的旧条目意味着服务保留了旧注册。
挪走监听器有注册表修改看不出来的代价。普通 SSTP 客户端拨的是 443,因此每个客户端配置都要换成新端口,而这条流量对任何检查路径的东西来说也不再像 Web 请求。在只容忍看起来像 HTTPS 的流量的网络里,这基本抽掉了当初选择 SSTP 的理由。小而受管的客户端群体能承受这一点;面向公众或 BYOD 的用户群不能。
另一种做法是把 443 留给 IIS,把 Apache 当作后端,由代理转发过去。它保住了 SSTP 的默认值,也让端口只有一个主人,代价是转发头、应用再也看不到的客户端地址,以及一个必须说代理真正所发协议的后端。
按顺序证明主人、证书和握手
一条完成的 TCP 连接只能证明存在监听器,别的什么都证明不了,所以隧道仍然失败时端口扫描看起来却可能很健康。四个阶段以不同方式失败,而针对你没弄坏的那一阶段去修,通常会让冲突更深。按顺序逐个排查,在答案分岔的地方停下。
netstat -aon | findstr :443
certutil -store My
Get-WinEvent -LogName System -ProviderName RasSstp -MaxEvents 20
第一行说出持有 443 的 PID,它应当是刚配置好的那个服务。第二行列出带 subject 的计算机证书,因此与客户端配置里的地址对不上的名称会立刻显眼。事件查询返回最近的 SSTP 记录,而一次从未走到 PPP 认证的握手,留下的记录与通过认证而后被拒绝的那次不同。最后阶段只在客户端上可见,那里的条目要么报告已连接,要么点出 PPP 错误。
当一台主机必须兼做两件事时,有意识地选择 443 的主人并把选择写下来,否则下一个开启某项功能的人会重新发现这次冲突。拆分角色仍然是更干净的答案:公共 Web 服务器和远程接入端点各自拥有补丁、证书和重启周期,这些周期按各自的节奏相遇。无论如何,把监听检查、注册表路径和选定的端口写进交接记录。