记一次由 MTU 引发的“血案”:FRP 反向代理重启后神秘失效的排查之旅
引言:风平浪静下的“意外”
对于我们这些热衷于 Self-Hosted 的玩家来说,一个稳定运行的家庭服务器集群是最大的慰藉。我的 PVE 平台上,通过 Docker 和 1Panel 管理着一系列服务,并使用 FRP (Fast Reverse Proxy) 将部分 Web 应用安全地暴露到公网。一切都完美运行,直到我进行了一次再普通不过的操作——重启了 frpc 客户端容器。
瞬间,所有通过公网域名访问的服务全部瘫痪。浏览器无情地展示着 ERR_HTTP2_PROTOCOL_ERROR,仿佛在嘲笑我的天真。然而,通过局域网 IP 访问一切正常。直觉告诉我,问题出在了 FRP 或反向代理的链路上。一场耗时数小时的“破案”之旅,就此拉开序幕。
排查之旅:层层迷雾,柳暗花明
第一站:怀疑“嫌疑人”—— FRP 与后端服务
按照常规思路,我首先检查了最明显的“嫌疑人”:
- FRP 客户端 (
frpc.toml) 配置:反复检查了代理规则,特别是web-reverse-proxy相关的部分,type确实是tcp,localIP和localPort也都指向了我的 OpenResty 网关。配置无懈可击。 - 后端服务 (1Panel/OpenResty):通过局域网 IP
https://192.168.0.x访问,服务完全正常。这排除了后端服务崩溃的可能。
初步排查陷入僵局。配置正确,服务正常,那问题出在哪?
第二站:关键线索 —— ERR_HTTP2_PROTOCOL_ERROR 与 MTU
这个错误码通常指向网络协议层面的问题,特别是与 HTTPS (SSL/TLS) 协商有关。一个资深的技术专家 AI 助手提示我,这很可能与 MTU (Maximum Transmission Unit, 最大传输单元) 有关。
我立刻在运行 frpc 的服务器上执行了 ping 测试:
ping -D -s 1472 公网IP # -D: 不分片, -s 1472 + 28 头部 = 1500 字节
结果立刻出现了关键线索:
From 192.168.0.1 icmp_seq=1 Frag needed and DF set (mtu = 1480)
翻译:我的路由器 (192.168.0.1) 明确告诉我,它无法在不分片的情况下发送 1500 字节的数据包,它到公网的路径 MTU 只有 1480!
理论:HTTPS 握手时的数据包较大,当 frpc 发送一个接近 1500 字节的握手包时,在路由器处因超出 MTU 限制而被丢弃,导致握手失败,浏览器报出协议错误。
第三站:悖论出现 —— 为什么是“现在”?
找到了 MTU 问题,似乎离真相只有一步之遥。但我立刻向 AI 提出了一个无法回避的悖论:“如果 MTU 问题一直存在,为什么我的服务在重启前是好的,偏偏在重启后才出问题?”
一个静态的网络问题,不应该因为一个应用重启而突然显现。这个疑问,将我们引向了问题的真正核心——Docker 网络。
第四站:深入 Docker —— network_mode: host 的“背刺”
我最初的猜测是 Docker 的 bridge 网络 MTU 设置问题。我准备修改 docker-compose.yml 为 Docker 网络指定 MTU,但就在这时,我检查了 1Panel 为我生成的 frpc 容器配置,一个关键设置映入眼帘:
services:
frpc:
# ...
network_mode: host
# ...
network_mode: host!这意味着 frpc 容器没有使用 Docker 隔离的虚拟网络,而是直接共享了宿主机的网络栈。
这瞬间让所有线索都串联了起来:
- 共享网络:容器的网络配置(包括 MTU)完全等同于宿主机。
- 宿主机 MTU:我的 PVE 宿主机,其物理网卡 MTU 默认是 1500。
- 重启触发:重启
frpc容器后,进程重新初始化,它从宿主机环境读取到 MTU 为 1500,于是放心地发送大数据包。 - 冲突发生:这个 1500 的数据包在路由器处遇到了 1480 的瓶颈,被丢弃。
真相大白:一直存在的网络 MTU 缺陷,被一次 Docker 容器的重启“激活”了。容器在 host 模式下,忠实地继承了宿主机错误的 MTU “认知”,导致了这场“血案”。
最终解决方案:校准源头
既然问题根源在宿主机,那么解决方案也必须在宿主机上。我的宿主机是 Proxmox VE (PVE),它基于 Debian。
原则:只修改那个配置了通往路由器网关(192.168.0.1)的网桥。在我的环境中,它是 vmbr0。
操作:
- 登录 PVE Web UI。
- 导航到
数据中心->节点->系统->网络。 - 选中
vmbr0,点击编辑。 - 在 MTU 字段中,填入
1480。 - 保存后,点击顶部的
应用配置按钮,让更改热生效。
配置应用后,PVE 主机知道了通往外界的“门”只有 1480 那么宽,它会自动调整所有从这里发出的数据包尺寸。此时,重启 frpc 应用,再次访问公网域名,一切恢复正常!
总结与启示
这次看似离奇的故障,实际上是一堂生动的网络实战课。它带给我几个深刻的启示:
- 相信证据,但要追问“为什么”:
ping测试很快暴露了 MTU 问题,但“为什么重启后才出问题?”这个追问,才是找到根本原因的关键。 - 魔鬼在细节中:
network_mode: host是一个常见的 Docker 配置,但必须深刻理解它与宿主机网络的共生关系。 - 网络是端到端的木桶:整个通信链路的性能,取决于最窄的那块板(MTU)。校准源头,才能一劳永逸。
- 不要害怕底层:从应用层(FRP)到容器层(Docker),再到宿主机虚拟化层(PVE),最后到物理网络层(路由器),逐层排查,问题终将无所遁形。
希望这次的排查经历,能为同样在 Self-Hosted 道路上探索的你,提供一份有价值的参考。