跳过正文
  1. 博客文章/

pfSense 翻车记:一次防火墙重启引发的故障与恢复全过程

·729 字·4 分钟·
运维实战 故障复盘 PfSense 防火墙 故障排查 ZFS Restic IPv6 灾难恢复 运维复盘
Zayn
作者
Zayn
专注 Kubernetes、CI/CD、可观测性等云原生技术栈,记录生产环境中的实战经验与踩坑复盘。
目录
凌晨一条 IPv6 告警,引出了一场持续半个上午的连锁故障:盲目重启的恶果、U 盘上的 ZFS 崩盘、没网却要下载镜像的死循环、忘记的 restic 密钥、以及一个在第三方服务抽风时假装告警的监控链路。这篇文章按时间线完整复盘整个过程,并把每一个弯路都诚实地留下来,因为错误路径往往比正确路径更有教训。
一句话总结:凌晨收到 IPv6 告警,盲目重启 pfSense 后防火墙无法开机,连锁引发全屋断网;紧急通过异地 VPS 中转下载镜像,重装时果断放弃 ZFS,再从 restic 异地备份恢复配置,最后顺手自建 IPv6 查询服务替代掉作妖的第三方站点。不要在生产环境盲目重启,也不要在没有 ECC 的 U 盘上跑 ZFS。

故障时间线
#

先用一张图说明整件事的来龙去脉:

flowchart TD
    A[早晨监控告警
IPv6 地址丢失] --> B{登机排查
curl 6.ipw.cn 失败} B --> C[误判为 DHCPv6 续约失败] C --> D[在 pfSense 上执行 reboot] D --> E[全屋断网
钉钉告警全无] E --> F[赶到公司接显示器] F --> G[EFI loader 报错
Failed to find bootable partition] G --> H[启动 U 盘 ZFS 文件系统崩盘] H --> I[用备用 SSD 重装 pfSense
放弃 ZFS 改用 UFS] I --> J[通过 restic 异地备份恢复配置] J --> K[IPv6 仍异常
发现是 ipw.cn 服务问题] K --> L[自建 IPv6 查询服务
并修改 DDNS 数据源] L --> M[故障收敛]

一、故障缘由:一次看似普通的 IPv6 告警
#

早上监控系统弹出告警,提示某台机器的 IPv6 地址丢失。登上机器确认:

root@ipv6-dmz-lb-110-110:~# curl https://6.ipw.cn
curl: (6) Could not resolve host: 6.ipw.cn

监控告警截图

按以往经验,这类问题多半是上级防火墙的 DHCPv6 续约失败(运营商分配的 IPv6 前缀会定期变更)。我平时用来检测 IPv6 连通性的站点是 https://ipw.cn/ipv6/

登上防火墙一看,IPv6 地址其实是有的,但同样请求 6.ipw.cn 依然失败。我一拍脑袋判断是续租分配有问题,直接执行了 reboot——然后噩梦开始了。

重启之后,家里的远程服务器全部失联,钉钉告警消息一条都收不到。这才意识到:防火墙起不来了。

事后复盘:其实是 ipw.cn 这个第三方站点当时服务异常 + pfSense 的 U 盘磁盘本身处于亚健康状态,两个独立问题叠加,在重启的一瞬间集中爆发。

ping6 www.baidu.com 其实是通的,只是 curl 6.ipw.cn 解析失败:

root@ipv6-dmz-lb-110-110:~# ping6 www.baidu.com
PING www.baidu.com(240e:ff:e020:99b:0:ff:b099:cff1) 56 data bytes
64 bytes from ...: icmp_seq=1 ttl=54 time=5.91 ms
64 bytes from ...: icmp_seq=2 ttl=54 time=6.06 ms
--- www.baidu.com ping statistics ---
2 packets transmitted, 2 received, 0% packet loss
root@ipv6-dmz-lb-110-110:~# curl https://6.ipw.cn
curl: (6) Could not resolve host: 6.ipw.cn

如果当时稍微多做一步 ping6 验证,就不会贸然重启。

二、定位问题:Failed to find bootable partition
#

刷牙洗脸赶到公司,给防火墙接上显示器,屏幕停留在这样的画面:

EFI loader 报错

核心错误信息:

FreeBSD/amd64 EFI loader
...
USB(0x5,0x0)/...
...
Failed to find bootable partition

把截图甩给 Gemini 分析,结论也很清晰:

  • 系统环境:FreeBSD/amd64 EFI loader,这台机器是 pfSense;
  • 启动介质:路径里出现 USB(0x5,0x0),说明系统正从 U 盘启动;
  • 故障点:UEFI 识别到了 U 盘,EFI 引导程序也加载了,但依次尝试 disk0p1 ~ disk0p4 全部失败——引导能读到,但系统主分区已经挂掉

最可能的根因:普通 U 盘的闪存颗粒不适合长期作为系统盘频繁读写,更何况这台机器上跑的还是对写入极为敏感的 ZFS。

经验教训 1:pfSense / OPNsense / TrueNAS 这类 FreeBSD 系软路由/NAS,强烈不建议长期用 U 盘作为系统盘。哪怕用一块 60G 的二手 SSD,都比 U 盘靠谱十倍。

三、重装 pfSense:没网的时候怎么装需要联网的系统?
#

手头刚好有一块备用的 SATA SSD,准备直接重装。结果又撞上两堵墙:

问题 1:官方镜像依赖联网安装
#

从 pfSense 官网下载的最新镜像,在安装过程中会去拉远端包仓库。但此时:

  • 主 WAN 接在 pfSense 上,而 pfSense 已经挂了,所以笔记本没网;
  • 备用 WAN 接在 FortiGate 上,有网,但我笔记本没直连过去。

最后只能把笔记本连到手机热点上查资料,找到 Netgate 官方镜像站:

问题 2:镜像下载极慢
#

这个镜像站在国内裸连几乎跑不动。临时方案是用异地 VPS 做中转

flowchart LR
    A[Netgate 镜像站
atxfiles.netgate.com] -->|wget 高速直连| B[香港/新加坡 VPS] B -->|scp 回内网| C[我的笔记本] C -->|写入 U 盘| D[新的 pfSense 安装盘]

手里有香港和新加坡各一台 VPS,先在 VPS 上下载(海外速度非常快),再从 VPS scp 回本地,整体耗时比裸连直接下降一个数量级。

问题 3:文件系统选择——这次坚决不用 ZFS
#

安装时 pfSense 会让你选择文件系统。上一次我选的是 ZFS,也正是这次翻车的主要嫌疑人:

为什么 ZFS 在这台机器上不合适?

  • 这是一台普通 PC(没有 ECC 内存)+ Intel 四口网卡的软路由;
  • ZFS 设计上依赖 ECC 来对抗内存位翻转,在非 ECC 环境下反而可能加速数据损坏;
  • ZFS 的写放大 + 元数据频繁更新,会让低端 U 盘 / 廉价 SSD 的寿命大幅缩水
  • 上一块 SATA SSD 用了 ZFS 大约 1 年写爆,这次的 U 盘只扛了几个月。

这次我果断选择了 UFS(Auto - ZFS 的那个选项换成非 ZFS),安装过程更像 DD,直接把系统镜像写到盘上,装完开机即可用。

四、恢复配置:从 restic 异地备份中捞回 XML
#

pfSense 装好了,但还是一台光秃秃的新机器——所有 NAT 规则、VPN、VLAN、DHCP 映射都需要从备份恢复。

预案起了作用:独立于防火墙的异地备份
#

我的备份方案是 pfSense 定期把 config.xml 推送到 restic-rest-server,每 4 小时一次。关键决策是:

经验教训 2异地备份服务必须部署在防火墙故障时你仍能访问的网络里

我当时专门把 restic-rest-server 部署在一个特殊的局域网下。防火墙挂掉后,我依然能通过另一条路径连到这台备份服务器——这是整个故障中最救命的一步预案。

忘记的密码:重置 htpasswd
#

连上备份服务器后发现自己忘了 restic-rest 的 HTTP Basic Auth 密码。好在服务是 Docker 部署,直接 exec 进去重置:

root@5600x:/nvme1n1p1/docker-compose/restic-rest-server# \
  docker exec -it restic-rest-server htpasswd -B /data/.htpasswd pfsense-backup
New password:
Re-type new password:
Updating password for user pfsense-backup

临时把密码改成了 123456(事后记得改回强密码)。

忘记的密码 Part 2:restic 仓库加密密钥
#

restic 本身对仓库有独立的加密密钥,这个密钥在初始化仓库时设置,一旦丢失就真的拿不到数据了

一顿乱翻之后,在 docker-compose.yml 同级目录发现了一个自己当初写的清理脚本 prune.sh——这个脚本用来定期 forget 旧快照,里面硬编码了 RESTIC_PASSWORD

root@5600x:/nvme1n1p1/docker-compose/restic-rest-server# tree -L 2
.
├── data
│   └── pfsense
├── docker-compose.yml
└── prune.sh
#!/bin/bash
# restic 仓库定期清理脚本(服务端执行)
# 配合 --append-only 模式使用
set -e

RESTIC_REPO="/data/pfsense"
RESTIC_PASS="xxxx"   # <-- 就是这个,救了我一命

echo "=== $(date) 开始清理 restic 仓库 ==="

docker run --rm \
  -v "/nvme1n1p1/docker-compose/restic-rest-server/data/pfsense:/repo" \
  -e RESTIC_REPOSITORY="/repo" \
  -e RESTIC_PASSWORD="$RESTIC_PASS" \
  restic/restic:latest forget \
    --keep-daily 30 \
    --keep-weekly 8 \
    --keep-monthly 12 \
    --prune 2>&1

这是个双刃剑的教训

  • 好的一面:密钥写在脚本里,紧急情况下救了我;
  • 坏的一面:密钥明文存放在同一台服务器上,如果这台服务器本身被入侵,加密就形同虚设。

更好的做法是把 restic password 放到 1Password / Bitwarden / Vault 里,同时在本地留一份离线纸质备份。

拉取备份并恢复
#

拿到密钥后,先列出快照,再按 ID 恢复:

# 列出所有快照
restic -r rest:http://pfsense-backup:123456@10.16.110.17:8100/pfsense snapshots

# 恢复指定快照到本地目录
restic -r rest:http://pfsense-backup:123456@10.16.110.17:8100/pfsense \
  restore b590bfab --target /tmp/pfsense_backup_1_b590bfab

我一口气拉了最近 5 个快照以防某一个文件坏掉:

restic 恢复截图

然后笔记本直连 pfSense 的 LAN 口,自动获取 DHCP,访问网关 Web UI,在 Diagnostics - Backup & Restore 页面上传 XML,一键全量恢复。内网瞬间满血复活。

五、顺便解决根因:自建 IPv6 查询服务
#

恢复之后再次到业务机器上 curl 6.ipw.cn依然失败。这一次我没有再盲目重启,而是交叉验证了一下:

ping6 www.baidu.com       # 正常
curl -6 ifconfig.co       # 正常
curl https://6.ipw.cn     # DNS 解析失败

根因水落石出:ipw.cn 的 IPv6 查询服务本身在抽风。 原来的告警是误报,只是我用它来做 IPv6 可用性判断,导致监控系统以为我没有 IPv6。

治本方案:别再依赖第三方服务。让 Claude Code 帮我写了一个极简的 IPv6 查询服务,部署到自己的机器上。已经开源:

对外暴露的地址:

# 自动识别(客户端用 v4 就返回 v4,用 v6 就返回 v6)
curl http://42.194.147.52:8080/

# 强制 IPv6
curl -6 http://[2402:4e00:c011:2000:9c:aba0:785a:0]:8080/

配置完后再把 DDNS 里用来获取 IPv6 的数据源地址也换成这个自建服务,整条链路就完全掌握在自己手里了:

DDNS 配置截图

六、复盘与教训
#

这次事故从告警到恢复,前后花了大半个上午。写在最后,是几条我想烙在脑子里的经验:

教训 1:不要在生产系统上盲目重启
#

“重启大法好” 在个人电脑上成立,在生产系统上是一句非常危险的话。

很多平时没有暴露的问题(磁盘坏块、内存坏块、未 flush 的配置、systemd 启动顺序问题),都会在重启的那一瞬间被一次性引爆。重启前至少先确认:一旦起不来,你有没有办法把它救回来。

教训 2:不要在 U 盘上跑 ZFS(尤其是没有 ECC 的机器)
#

  • pfSense 已经连续两次因为 ZFS 把我的存储写爆:
    • 上一次是一块 SATA SSD,扛了一年多
    • 这一次是临时应急的 U 盘,只扛了几个月
  • 普通 PC 没有 ECC 内存,ZFS 的优势(数据完整性校验)反而是一种负担;
  • 这次重装我已经改用 UFS,后续观察寿命表现。

教训 3:备份服务一定要放在"故障域之外"
#

这次能快速恢复的关键,是我当初把 restic-rest-server 部署在一个不经过 pfSense 就能访问的网络里。如果备份服务藏在 pfSense 后面,这次就只能含泪从零开始重配所有规则了。

自检问题:你的备份系统,在你的防火墙 / 主路由挂掉时还能访问吗?

教训 4:密钥管理需要"多级冗余"
#

  • 服务密码可以随时 htpasswd 重置,这没问题;
  • 但 restic 的仓库加密密钥一旦丢失就是毁灭性的
  • 这次靠 prune.sh 脚本里的明文密码救了命,但这不是一个安全的长期方案;
  • 改进:密钥存 1Password + 一份离线打印件 + 关键运维脚本里可以保留但要做好访问控制。

教训 5:关键依赖不要放在第三方免费服务上
#

整件事的导火索,其实是 ipw.cn 这个第三方 IPv6 查询站点抽风。监控系统依赖它来判断 IPv6 可用性,它一挂就会触发假告警。自建一个 10 行代码的服务,就可以一劳永逸地解决。


七、下一步改进
#

基于这次教训,后续会做以下完善:

  1. 制作离线 pfSense 安装介质:预先下载好 ISO 并保存到 NAS,避免"防火墙挂掉才发现镜像下不下来"的死循环;
  2. 运维手册(Runbook):把 restic 恢复命令、备份地址、密钥位置写成一份加密文档,放到 1Password;
  3. 监控加 Fallback 判断:IPv6 可用性检测同时查 自建服务 + ping6 + 第三方,任意一个通过就不告警;
  4. Infrastructure as Code:pfSense 配置除了 XML 备份之外,再用 Ansible / pfsense-api 做一份"人类可读"的版本化配置。

目标:下次再出现类似事故,从告警到完全恢复,能压缩到 30 分钟以内

这次踩坑就当作一次真实的灾难演练。运维最宝贵的不是从不翻车,而是每翻一次车都能让下次的 MTTR(平均恢复时间)更短一点

相关文章

Windows CAD 工作站监控体系实战:Categraf v0.5.5 + 夜莺 + GPU/TCP 深度采集
·2817 字·14 分钟
可观测性 虚拟化 Categraf 夜莺 Nightingale 监控 可观测性 Windows Server Nvidia_smi GPU监控 PowerShell PromQL Grafana Observability
Windows RDS 下 Illustrator 2025 普通用户 0xc0000142 崩溃修复
·3255 字·16 分钟
Windows 调试 Windows Server RDS Adobe Illustrator 0xc0000142 PerSessionTempDir Std::filesystem Minidump WER Debugging MSVC C++ Exception
在 RDP 远程桌面中运行 PADS:从 API Hook 到 AppInit_DLLs 的工程实践
·1165 字·6 分钟
逆向工程 Windows Windows Api-Hook Detours Pads Rdp Github-Actions Ci-Cd