自己把自己关在门外:一次 UFW 把 SSH 锁死的排查记录
这次踩了一个挺经典的坑:我在云服务器上折腾 frp 的时候,顺手开了 UFW 防火墙,结果把自己 SSH 入口给挡住了。
服务器没死,服务也没坏,但我就是连不上。
最后发现:不是服务器出问题,是我自己把自己关在了门外。
事情背景
我有一台云服务器,上面跑了一些服务:
Caddy / Nginx
Docker
frp
一些 Go / Python / Node / Bun 服务
最近在折腾 frp,用来做内网穿透和服务分流。
因为涉及端口暴露,我当时想着:“要不顺手把 UFW 开起来,做一层本机防火墙?”
这个想法本身没错,但问题是:我这是一台云服务器。
云服务器本来就有一层安全组,比如阿里云安全组、腾讯云安全组、AWS Security Group 之类的。公网入口本来就可以在云后台控制。
结果我在云安全组之外,又叠了一层 UFW。
然后,事故来了。
现象:VNC 能进,SSH 进不去
最开始发现问题是:
SSH 22 端口连不上
但是云后台 VNC 可以连接
这就很有意思。
如果服务器彻底挂了,VNC 大概率也有问题。
但现在 VNC 能进,说明:
服务器还活着
系统还能登录
网络大体没死
问题就集中在:
sshd 服务
22 端口监听
服务器防火墙
云安全组
本地网络 / 代理
第一步:检查 ssh 服务
通过云后台 VNC 登录服务器后,先看 SSH 服务状态:
systemctl status ssh --no-pager
一开始看到的是:
ssh.service - OpenBSD Secure Shell server
Loaded: disabled
Active: inactive (dead)
TriggeredBy: ssh.socket
当时第一反应是:SSH 服务没起来。
于是启动它:
systemctl start ssh
systemctl enable ssh
再检查:
systemctl status ssh --no-pager
这次服务正常了:
Active: active (running)
Server listening on 0.0.0.0 port 22
Server listening on :: port 22
这说明 sshd 已经开始监听 22 端口。
再看端口:
ss -lntp | grep ':22'
确认 22 确实在监听。
但问题是:外面还是连不上。
第二步:本机测试 SSH
这时候要区分一个问题:
到底是 SSH 服务坏了,还是公网入口进不来?
在服务器 VNC 里执行:
ssh -v root@127.0.0.1
结果能进入 SSH 握手流程,出现类似:
Are you sure you want to continue connecting (yes/no/[fingerprint])?
这说明:
本机 SSH 是通的
sshd 没坏
22 端口本机可访问
所以问题不在 SSH 服务本身。
那就只剩下:
公网安全组
服务器防火墙
宝塔防火墙
本地网络 / 代理
第三步:破案,UFW 把我挡住了
继续检查防火墙:
ufw status
然后我突然想起来:我之前为了 frp,自己开过 UFW。
这下就破案了。
本质上是:
sshd 正常
22 端口正常监听
但是 UFW 没放行 22
所以公网 SSH 进不来
这就是典型的自己把自己锁在门外。
如果只是临时修复,可以执行:
ufw allow 22/tcp
然后再看:
ufw status verbose
但我这次的结论是:这台云服务器上暂时不需要 UFW。
因为公网入口已经由阿里云安全组控制了。
所以我直接关闭 UFW:
ufw disable
确认:
ufw status
看到:
Status: inactive
第四步:防止 UFW 重启后又复活
我一开始担心:现在是 inactive,那重启之后会不会又自己起来?
于是检查 systemd 状态:
systemctl is-enabled ufw
结果显示:
enabled
再看:
systemctl status ufw --no-pager
可以看到:
Loaded: loaded
Active: active (exited)
这里容易误解。
UFW 的 systemd 服务是 active (exited),不代表它像普通服务一样一直跑在后台。它更像是启动时加载规则,加载完就退出。
但既然我决定不用它,那就别留隐患。
先禁用开机启动:
systemctl disable ufw
如果想彻底防止它被误启动,还可以 mask:
systemctl mask ufw
执行后会看到类似:
Created symlink /etc/systemd/system/ufw.service → /dev/null.
这表示 UFW 服务被直接按住了。
再次检查:
systemctl is-enabled ufw
正常会显示:
masked
这下基本不用担心 UFW 自己复活了。
这次真正的问题不是 UFW,而是安全边界混乱
这次坑的本质不是 UFW 有问题。
UFW 是好东西。
问题是我在没有明确规划的情况下,把安全控制层叠得太多了:
阿里云安全组
服务器 UFW
宝塔防火墙
Docker 端口映射
Caddy / Nginx 反代
frp 端口转发
这些东西每一层都可能影响访问。
一旦出问题,排查复杂度会直接翻倍。
比如 SSH 连不上,到底是:
阿里云安全组没放?
UFW 拦了?
宝塔防火墙拦了?
sshd 没启动?
sshd 配置错了?
端口改了?
本地代理搞错了?
如果没有清晰的边界,就很容易排查到怀疑人生。
我现在更推荐的云服务器安全模型
对于云服务器,我现在倾向于这样分层:
公网入口控制:云厂商安全组
HTTP/HTTPS 入口:Caddy / Nginx
内网穿透:frp
业务服务:尽量只监听 127.0.0.1 或 Docker 内网
本机 UFW:暂时不用,除非规则非常清晰
也就是说,公网入口主要交给阿里云安全组。
阿里云安全组只放必要端口:
22 SSH,最好限制为自己的 IP
80 HTTP
443 HTTPS
7000 frps bindPort,按需开放
业务服务端口不要乱暴露公网。
例如:
8000
8120
8122
2368
3000
这些端口如果能走 Caddy 反代,就不要直接开放到公网。
frp 场景下端口应该怎么想
frp 很容易让人产生一个误区:
“我是不是要把所有业务端口都开放?”
其实不应该。
比较清晰的方式是:
公网只开放 Caddy 的 80/443
frps 只开放必要的 bindPort
具体业务服务通过域名分流
比如:
a.example.com -> Caddy -> frp -> 内网服务 A
b.example.com -> Caddy -> frp -> 内网服务 B
api.example.com -> Caddy -> frp -> 内网 API
这样公网入口还是统一的。
不需要每新增一个服务,就去云后台开放一个新端口。
更不需要在 UFW、宝塔、阿里云安全组里同时维护一堆端口。
以后开 UFW 的正确姿势
如果以后确实要用 UFW,正确顺序应该是:
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable
ufw status verbose
一定要先放行 SSH,再启用 UFW。
如果是远程服务器,最好先开一个备用登录方式,比如:
云后台 VNC
另一个 SSH 端口
云厂商救援模式
否则一条命令就可能把自己锁外面。
如果已经锁了,那就只能通过云后台 VNC 登录进去修。
常用排查命令整理
SSH 服务状态:
systemctl status ssh --no-pager
启动 SSH:
systemctl start ssh
systemctl enable ssh
检查 22 端口监听:
ss -lntp | grep ':22'
本机测试 SSH:
ssh -v root@127.0.0.1
查看 UFW 状态:
ufw status verbose
放行 SSH:
ufw allow 22/tcp
关闭 UFW:
ufw disable
禁用 UFW 开机启动:
systemctl disable ufw
彻底禁止 UFW 被启动:
systemctl mask ufw
恢复 UFW:
systemctl unmask ufw
systemctl enable ufw
ufw enable
查看 SSH 日志:
journalctl -u ssh --since "1 hour ago" --no-pager
实时看 SSH 日志:
journalctl -u ssh -f
这次的教训
这次最大教训是:
云服务器的安全入口一定要分层清楚。
不要一边用阿里云安全组,一边开 UFW,一边又用宝塔防火墙,然后 Docker 还暴露一堆端口。
不是不能这么做,而是你必须非常清楚每一层在管什么。
否则出问题的时候,你连门到底是被谁锁上的都不知道。
我这次就是典型案例:
服务器没死
SSH 没坏
22 端口也在监听
但公网就是连不上
最后发现是自己开的 UFW 拦住了
一句话总结:
我不是被黑客挡在门外的,是被自己挡在门外的。
这事挺尴尬,但也挺值。
因为修好的不只是 SSH,而是我对云服务器安全边界的理解。
陕公网安备61011302002223号