自己把自己关在门外:一次 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,而是我对云服务器安全边界的理解。

Read more

Mac 上 Skill CLI 无法执行的坑:最后其实一条命令就够了

我在做 Amazon skills 的过程中,逐步把本地 CLI 从 Python 脚本切到 Go 二进制。这样做的好处很明显:用户不用装 Python、不用配依赖,解压 skill 后直接运行。但在 macOS 上,我们反复遇到一个看起来很玄的问题:同一个二进制,在 Linux/Windows 上正常,在 Mac 上就是执行不了。 当时遇到的现象 常见报错大概有几类: * 双击或 agent 调用 CLI 时,系统提示文件来自未知开发者,无法打开。 * 终端里执行时提示 Permission denied。 * 已经 chmod +x 了,仍然被 macOS 拦截。 * Apple

By ladydd

当我把全世界人群的基因 PCA 跑出来后,看见了一个倒 L 型

最近我把之前学的一些分子人类学知识,终于真正落地了。 不是停留在看论文、看别人画图、看别人解释“人群结构”这些概念,而是自己把数据处理完,自己跑 PCA,自己把全世界不同人群放到一张图上。 然后那一刻,我真的被击中了。 图上出现了一个非常漂亮的倒 L 型。 一端是非洲,另一端逐渐拉向东亚,中间有中东、欧洲、南亚、欧亚大陆上的各种过渡人群。它不是那种随机散点图,而是有方向、有骨架、有历史感的结构。 我第一眼看到的时候,脑子里直接冒出一句话: 这不像是一张普通统计图,这像是人类迁徙史在二维空间里留下的影子。 当然,后来我也提醒自己,PCA 不能被过度浪漫化。它不是地图,也不是时间轴,更不是“谁从哪里走到哪里”的直接证据。PCA 本质上是把高维基因差异压缩到几个主成分上,用最大方差方向把样本摊开。它可以帮助我们观察人群结构、相似性、分化和混合,但不能单独承担全部历史解释。PCA 在群体遗传学里常用于观察 population structure

By ladydd

吞吐与延迟:一个厨房比喻讲透性能压测

写于 2026-06-26。背景: MCP 服务 跑在 3 台 ClickHouse(每台 16 核 / 64G,1 分片 3 副本)上。 我们花了一整轮做公网压测,把这套系统的极限、天花板和杠杆全摸清了。这篇把"吞吐 / 延迟 / 排队"这三个最容易混的概念讲透,配我们自己的真实实测数据。 一句话结论 我们这套系统的吞吐天花板 ≈ 76 req/s。 往里塞再多并发(100、200、300、500),每秒"做完"的还是大约 76 个,多出来的全在排队。系统不会崩,只会让每个人等得更久。 天花板能不能抬?

By ladydd

四卡 3090 本地模型部署复盘:Ollama 跑通 35B,以及 GPU0 掉卡问题

这次做的是一轮真实的本地模型部署摸底。 目标不是搭一个临时 Demo,而是把一台四卡 3090 GPU 机器接进自己的日常 AI 使用环境:本机跑 Open WebUI,负责账号、会话和前端配置;GPU 机器只负责模型推理。这样以后换模型、换推理框架、重启服务,都尽量不影响本机的使用入口。 最后结论比较清楚:qwen3.5:35b 的 GGUF Q4_K_M 量化版已经通过 Ollama 跑通,本机 Open WebUI 可以接入,热加载后的聊天速度也能用;但 GPU0 存在明显稳定性问题,重启后能短暂恢复,跑过负载后又会掉到 NVML 异常状态。 状态快照 当前能用的部分: * 本机 Open WebUI 已部署,

By ladydd
陕公网安备61011302002223号 | 陕ICP备2025083092号