目录
[1. 现场:面板是绿的,日志是脏的](#1. 现场:面板是绿的,日志是脏的)
[2. 起点比想象中更糟:SSH 的默认配置](#2. 起点比想象中更糟:SSH 的默认配置)
[3. 证据:215 条失败记录](#3. 证据:215 条失败记录)
[4. 第一个动作:把 Fail2ban 装上](#4. 第一个动作:把 Fail2ban 装上)
[5. 面板上的六个参数,藏着一个依赖](#5. 面板上的六个参数,藏着一个依赖)
[6. 真正的盲区:ufw 从来没启用过](#6. 真正的盲区:ufw 从来没启用过)
[7. 根因:安装脚本对"没开防火墙"只记一行日志](#7. 根因:安装脚本对"没开防火墙"只记一行日志)
[8. 第二个盲区:规则里躺着的 11 个端口](#8. 第二个盲区:规则里躺着的 11 个端口)
[9. 修复:先把门装上,再收窄](#9. 修复:先把门装上,再收窄)
[9.1. 顺序:先放行 22,再 enable](#9.1. 顺序:先放行 22,再 enable)
[9.2. 删掉多余的放行](#9.2. 删掉多余的放行)
[9.3. 让服务本身不对外监听](#9.3. 让服务本身不对外监听)
[10. 验证:三条必须拿到的证据](#10. 验证:三条必须拿到的证据)
[11. 检查清单](#11. 检查清单)
[12. 写在最后](#12. 写在最后)
[13. 参考资料](#13. 参考资料)
前言:
1Panel 装好后 Fail2ban 显示封了 2 个 IP,SSH 也关了密码认证,看起来安全。直到敲下
ufw status,输出 inactive。这篇复盘用真实截图和安装脚本源码,把这条盲区挖到底。个人主页:艺杯羹
1. 现场:面板是绿的,日志是脏的
2026-11-14 上午,我在一台云服务器上装完 1Panel v1.10.34-lts。系统是 Ubuntu 22.04,内核 5.15.0-203-generic,4 核 / 3.82 GB 内存 / 38.58 GB 磁盘。

首页上"网站 0、数据库 0、计划任务 0"三个零,说明这台机器还是干净的初始状态。CPU、内存、磁盘三块仪表都在正常区间,运行时间 5 小时 49 分,没有任何红色提示。
这类界面最容易让人提前收工。面板能装、站点能开、容器能起,看起来该做的都做了。
但面板首页反映的只是应用层。真正的问题在系统层,而系统层不会主动弹窗。
2. 起点比想象中更糟:SSH 的默认配置
第一个该看的是 SSH 管理页。

四项内容:
- 连接端口:22
- 监听地址:
0.0.0.0:22(IPv4)和:::22(IPv6) - root 用户:允许 SSH 登录
- 密码认证:打开
翻译成一句人话:公网上任何一个 IP,都可以直接对着 root 账号猜密码,不限来源、不限次数。
22 端口是全网扫描器第一个会敲的门,root 是它们第一个会试的账号,密码认证打开则意味着"猜对了就能进"。这三件事凑在一起,等于把钥匙孔直接对着大街。
这里不是 1Panel 的错。它只是如实展示了 sshd 的状态,而 Ubuntu 的 sshd 默认就是允许 root、允许密码。
3. 证据:215 条失败记录
装完面板还不到六小时,登录日志已经很难看。


这里有个细节值得记下来:45.148.. 这个荷兰 IP,用同一个端口 55334,在 08:33:06、08:33:14、08:33:18 连试三次。间隔只有几秒,说明对面不是人在敲键盘,是脚本在跑循环。
再看总数:

215 条。 这台机器上线还不到一天,面板运行时间显示 5 小时 49 分。
从日志里抽几条,能看清攻击的形态:
| 登录 IP | 归属地 | 端口 | 登录方式 | 用户 | 结果 | 时间 |
|---|---|---|---|---|---|---|
| 45.148.. | 荷兰 | 55334 | 密码 | root | 失败 | 08:33:18 |
| 106.51.. | 印度 | 32770 | 密码 | root | 失败 | 08:32:14 |
| 37.239.. | 伊拉克 | 63021 | 空 | root | 失败 | 08:28:26 |
| 193.47.. | 美国 | 30406 | 密码 | root | 失败 | 08:26:06 |
| 92.118.. | 美国 | 37698 | 密码 | root | 失败 | 06:05:36 |
| 14.103.. | 中国广东 | 54336 | 密码 | root | 失败 | 06:02:40 |
| 182.66.. | 印度 | 46766 | 密码 | invalid | 失败 | 05:52:11 |
| 176.53.. | 土耳其 | 16820 | 密码 | invalid | 失败 | 05:42:25 |
表中的 IP 后两段已脱敏,前缀保留,用来体现攻击来源的网段分布。
两个特征:
- 端口全在 30000 至 60000 之间随机跳,这是扫描器随手挑源端口留下的痕迹。
- 用户字段有
root,也有invalid。invalid的意思是连用户名都没猜对,直接被 sshd 判为不存在的用户。
第 2 点其实说明了一件事:对方连这台机器上有哪些账号都不知道,就已经在批量试了。它们不需要了解目标,只需要 22 端口开着。
4. 第一个动作:把 Fail2ban 装上
1Panel 工具箱里的 Fail2ban 当时是空的。

于是进终端装:

装的是 0.11.2-6,Ubuntu 22.04 源里的版本,依赖 python3-pyinotify 和 whois。装完 systemd 自动建了软链接,fail2ban.service 已经在 multi-user.target.wants 里。
立刻查 jail 状态:
text
root@server:~# fail2ban-client status sshd
Status for the jail: sshd
|- Filter
| |- Currently failed: 2
| |- Total failed: 38
| `- File list: /var/log/auth.log
`- Actions
|- Currently banned: 2
|- Total banned: 2
`- Banned IP list: 106.51.*.* 176.53.*.*

注意封禁列表里的两个 IP:106.51.*.* 在 08:32 刚试过 32770 端口,176.53.*.* 在 05:42 试过 16820 端口。前面日志里的攻击者,在这里变成了被封禁的对象,证据链正好接上。
看到 Currently banned: 2 那一下确实松了口气。晚些时候再查,Total banned 已经涨到 4,Banned IP list 换成了 45.148.10.* 段的另一台和 107.180.*.*------后者在 09:20 还在试密码,然后被收了进去。
5. 面板上的六个参数,藏着一个依赖
进 Fail2ban 的基础配置页。

六个值分别是:
- 监听 SSH 端口:22
- 最大重试次数:5
- 禁用时间:600 秒
- 发现周期:300 秒
- 禁用方式:ufw
- 日志路径:
/var/log/auth.log
前五个都好理解:5 分钟内失败 5 次就封,封 10 分钟。10 分钟这个值太短,对方喝口水就能再来,于是改成 86400 秒。

改完这一项,看起来就齐了。但"禁用方式:ufw"这一行没动,当时也没多想------ufw 是 Ubuntu 的默认防火墙工具,填它看起来天经地义。
问题恰恰出在这一行。
6. 真正的盲区:ufw 从来没启用过
后来为了确认防火墙状态,敲了一条命令:
text
=== UFW RULES PREP ===
Status: inactive
inactive。 这台机器上没有任何包过滤在运行。
而面板里"禁用方式"写的正是 ufw。
这两件事放在一起,性质就变了。Fail2ban 在"该封谁"这件事上是清醒的,在"怎么封"这件事上却指向了一个没启动的服务。ufw 没启用时,ufw insert ... deny 这类写入动作不会真正落到内核里。
排查到这里,要先证伪几个看起来更合理的猜测,否则方向会越跑越偏:
| 猜测 | 验证方式 | 实际结果 |
|---|---|---|
| Fail2ban 服务没起来 | systemctl status fail2ban |
服务在跑,有常驻进程 |
| jail 监听错了日志文件 | status sshd 里的 File list |
/var/log/auth.log,Ubuntu 上的正确路径 |
| 封禁规则真的进了内核 | iptables -S 查 INPUT 链 |
banned 计数不能代替这一步 |
关键在第三行:Currently banned 是 Fail2ban 自己账本上的数字。它只说明"Fail2ban 认为这两个 IP 该封",不说明"内核真的把它们挡在门外"。
这两套东西是分开的:计数器涨了,动作却可能在一直失败,而且默认不会弹窗告诉任何人。一个只读面板的人,看到的就是一片绿色。
7. 根因:安装脚本对"没开防火墙"只记一行日志
问题往上追,会追到 1Panel 的安装脚本。里面有个 Set_Firewall 函数,Debian/Ubuntu 分支走 ufw。把无关分支去掉,骨架是这样:
bash
# 摘自 1Panel 官方安装脚本(第 183 至 211 行),只保留 ufw 分支
if which ufw >/dev/null 2>&1; then
if [[ $(service ufw status >/dev/null 2>&1 && echo active || echo inactive) == 'active' ]]; then
ufw allow "$PANEL_PORT"/tcp # 情况 A:ufw 已启用,放行面板端口
ufw reload
elif [[ $(service ufw start >/dev/null 2>&1 && echo Success || echo Faild) == 'Success' ]]; then
ufw allow "$PANEL_PORT"/tcp # 情况 B:ufw 能启动,先启动再放行
ufw reload
else
log "$TXT_FIREWALL_NOT_ACTIVE_IGNORE" # 情况 C:起不来,只记一行日志
fi
fi
最后那个 else 对应的日志文案是"防火墙未启用,忽略"。三个动作概括:跳过、不启用、不提醒。
这段代码的设计假设很清楚------装面板之前,防火墙已经配好了。没配好也不会中断安装,不会在面板首页挂一个待处理项,更不会阻止继续操作。整个安装过程风平浪静,结束时就是一个功能齐全的 1Panel。
这就是面板类工具的共同分界:它们管应用层,对系统层的安全状态只做默认假设,不做强制校验。
8. 第二个盲区:规则里躺着的 11 个端口
比"没有防火墙"更值得警惕的,是这台机器留下的另一处痕迹------它确实往 ufw 里写过规则:
text
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw allow 3306/tcp
ufw allow 8080/tcp
ufw allow 8081/tcp
ufw allow 8085/tcp
ufw allow 8088/tcp
ufw allow 8089/tcp
ufw allow 8090/tcp
ufw allow 18900/tcp
11 个。按风险排一遍:
| 端口 | 常见用途 | 风险判断 |
|---|---|---|
| 22 | SSH | 必须开,但要配合密钥登录与 Fail2ban |
| 80 / 443 | Web 入口 | 必须开 |
| 3306 | MySQL / MariaDB | 高危,数据库不该直接面对公网 |
| 8080 至 8090 | 各类内部服务与调试端口 | 高危,典型的"上线后忘了收" |
| 18900 | 内部服务或面板调试端口 | 中高危,没有对外的理由 |
这里藏着比"没开防火墙"更隐蔽的一层错觉:规则写进去了,防火墙却是 inactive。看规则以为关着,实际全开着。
规则列表本身会骗人。能相信的只有 ufw status 的第一行。
9. 修复:先把门装上,再收窄
9.1. 顺序:先放行 22,再 enable
顺序错了会把机器锁死。动防火墙之前,先确认 SSH 通道牢靠。
bash
sudo ufw allow 22/tcp # 必须第一步,否则 enable 时 SSH 会一起被挡在外面
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw --force enable
9.2. 删掉多余的放行
bash
sudo ufw delete allow 3306/tcp
sudo ufw delete allow 8080/tcp
# 8081、8085、8088、8089、8090、18900 逐条删除
sudo ufw status numbered # 删完逐条核对,确认只剩 22、80、443
9.3. 让服务本身不对外监听
防火墙是第二道锁,第一道锁是服务不要绑 0.0.0.0。
yaml
# docker-compose.yml 片段:内部服务只绑回环地址
services:
api:
ports:
- "127.0.0.1:8080:8080" # 对外统一由 Nginx 反代进入
db:
ports:
- "127.0.0.1:3306:3306" # 数据库不需要映射到公网
端口不监听,扫描器就扫不到,连被探测的机会都没有。这比防火墙更彻底。
然后回到 SSH,把密码认证关掉,只留密钥。关掉之后仍然能进,登录日志里能看到密钥登录成功的记录:

爆破没有停。同一批 IP 还在试:

10. 验证:三条必须拿到的证据
改完不算完,要有证据。
bash
# ① 防火墙真的活着
sudo ufw status verbose # 必须是 Status: active
# ② 封禁规则真的进了内核
sudo iptables -S | grep -i f2b # 应看到 f2b-sshd 链和对应 REJECT
# ③ 从外部验证端口关闭(不要在被测机器上执行)
nc -vz <公网IP> 3306 # 必须是 refused 或 timeout
| 验证项 | 合格标准 | 它证明了什么 |
|---|---|---|
ufw status |
Status: active |
包过滤真的在运行 |
iptables -S |
存在 f2b-sshd 链 |
封禁动作真的落到内核 |
外部 nc |
refused 或 timeout | 外部真的进不来 |
第三条最容易被跳过,也最关键。前两条只能证明"规则配了",第三条才证明"外部进不来"。
11. 检查清单
ufw status是activeufw status numbered只剩 22、80、443- 3306、6379、27017 没有对外放行
- 容器端口全部绑
127.0.0.1 sshd -T显示passwordauthentication nopermitrootlogin评估是否改为nostatus sshd的File list指向auth.log- 禁用方式与实际启用的防火墙一致
- 从外部
nc验证过关键端口 - 容器配置
restart: unless-stopped
12. 写在最后
这次排查真正改掉的不是某条命令,而是一个判断顺序:以前看到面板变绿就认为结束,现在会把"规则写了"和"规则生效了"当成两件事,分别验证。
1Panel 的安装脚本没有错,它做了合理假设;Fail2ban 也没有错,它确实在拦人。问题出在"装完就算完成"这个默认心理上------而安全恰好是最不能交给默认值的部分。
那 215 条失败记录还在日志里滚动。它们不是 Fail2ban 有用的证明,而是这台机器从上线第一天起,就一直有人在敲门的记录。
区别只在于,门到底装没装。