前言
最近我在折腾一台位于海外的 Debian 12 服务器,部署好 Caddy 准备做一个简单的 HTTPS 站点(Reference 镜像站)。本以为是一次平平无奇的部署,结果却在网络连通性上栽了个跟头。
现象非常诡异:IPv4 访问一切正常,但 IPv6 访问却极其不稳定。
特别是在国内使用 IPv6 网络访问时,经常出现网页加载转圈、超时,甚至直接无法连接。起初我以为是线路质量差或者是被“墙”干扰了,但经过一番排查,发现凶手竟然是 IPv6 网络中臭名昭著的 PMTU 黑洞(Path MTU Black Hole)。
这篇文章记录了从发现问题到完美解决的全过程,如果你也遇到了“IPv6 能 Ping 通但 HTTPS 打不开”的玄学问题,希望这篇经验能帮到你。
一、诡异的故障现象
服务器环境很简单:
- 系统:Debian 12
- 网络:IPv4/IPv6 双栈,网卡 MTU 默认 1500
- 服务:Caddy (HTTPS/443)
当我把域名解析做好,证书申请下来后,开始测试访问:
- 海外节点测试:IPv4/IPv6 均秒开。
- 国内 IPv4 测试:正常。
- 国内 IPv6 测试:灾难现场。ITDog 的拨测结果显示,国内大部分地区的 IPv6 连接均失败,只有零星几个节点能通。
第一反应:被墙了?
这是直觉反应。但我马上用命令行工具验证了一下:
# 本地测试 Ping(ICMP)
ping6 <服务器IP>
# 结果:延时稳定,不丢包
# 本地测试 TCP 连通性
nc -z -v -6 <服务器IP> 443
# 结果:Succeeded! 连接成功
这下我懵了。Ping 是通的,TCP 端口也是通的,说明 IP 没被封,路由也是可达的。 那为什么浏览器里就是打不开呢?
二、抽丝剥茧:定位卡死环节
既然 TCP 层没问题,那问题一定出在应用层握手阶段。我在客户端使用 curl 进行了进一步测试:
curl -6 -v https://<域名>/
输出结果非常有意思:
* Trying 2xxx:xxxx:...:443...
* Connected to <域名> (2xxx:xxxx:...) port 443 (#0) <-- TCP连接建立成功
* ALPN, offering h2
* ALPN, offering http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1): <-- 客户端发送 Hello
... (这里卡住了,长时间无响应) ...
* Recv failure: Connection reset by peer
卡在了 Client Hello 发送之后,迟迟等不到服务器的 Server Hello。
为什么会卡在这里?
HTTPS 的 TLS 握手阶段,服务器在收到 Client Hello 后,需要发送包含证书链的 Server Hello 等数据包。现在的 SSL 证书链通常比较大,很容易导致单个 TCP 数据包的大小接近链路的 MTU 上限。
这就引出了 IPv6 网络的一个关键特性——PMTU(Path MTU)。
在 IPv4 时代,如果包太大了,中间路由器会帮我们分片。但在 IPv6 协议中,中间路由器不再负责分片。如果一个包超过了中间某段链路的 MTU,路由器会直接丢弃该包,并返回一个 ICMPv6 Packet Too Big 的消息给发送者(也就是我的服务器),告诉它:“包太大了,请改小点再发”。
理论上,服务器收到这个 ICMP 消息后,会调整发包大小(这叫 PMTUD,路径 MTU 发现机制)。
但现实很骨感:互联网上有大量的中间设备(防火墙、路由),出于所谓的“安全”考虑,把 ICMPv6 消息给拦截了。
于是悲剧发生了:
- 服务器发出大包(TLS 握手包)。
- 中间链路因为包太大丢弃了,并试图发回 ICMP 报错。
- ICMP 报错被拦截,服务器收不到通知。
- 服务器以为包丢了,疯狂重传大包。
- 客户端一直收不到数据,最终超时。
这就是 PMTU 黑洞。
三、实锤验证
为了证实我的猜想,我需要在客户端用 ping 模拟不同大小的数据包,看看链路的实际承载能力。
Linux/Mac 下使用 ping6(Windows 下是 ping -6):
# 1. 尝试发送接近 1500 字节的大包
# -s 1452: 数据长度 (1452 + 40字节IPv6头 + 8字节ICMP头 = 1500)
# -M do: 禁止分片 (IPv6 默认就是禁止中间分片,这里显式指定)
ping6 -s 1452 -M do <服务器IP>
# 结果:100% packet loss (全丢)
# 2. 尝试发送小一点的包(比如 1400 字节)
ping6 -s 1352 -M do <服务器IP>
# 结果:0% packet loss (通了!)
真相大白。链路的实际 MTU 肯定小于 1500。当 HTTPS 握手的大包发出来时,直接掉进了黑洞。
四、解决方案:IPtables MSS Clamping
知道了原因,解决思路有三条:
- 改网卡 MTU:把服务器网卡 MTU 改小(比如 1280)。这虽然有效,但会降低所有通信的效率,不是最佳实践。
- 开启内核 PMTU 发现:依赖 ICMP,但前面说了,ICMP 经常被过滤,不可靠。
- MSS Clamping(推荐):利用防火墙规则,在 TCP 握手阶段,强制把 MSS(最大报文段长度)改小。
TCP MSS 是在三次握手 SYN 包中协商的。如果我们在服务器端,强制将发出的 SYN/SYN-ACK 包中的 MSS 值调低,那么客户端和服务器后续通信时,就会乖乖地把包切得更小,从而“钻”过那个狭窄的黑洞。
为什么不用 sysctl?
网上有教程说修改 net.ipv6.tcp_mss_max,但我发现在 Debian 12 的内核中并没有这个参数,或者无法直接生效。因此,使用 ip6tables 是最通用、最底层的解法。
操作步骤
我们需要安装 ip6tables 和持久化工具(防止重启失效):
apt update
apt install -y iptables netfilter-persistent
然后,添加一条防火墙规则。
这里我们将 MSS 限制在 1400 字节(这个值比较保守,通常 1420 也可以,但 1400 更稳):
# 配置OUTPUT链:针对本机发起的IPv6 TCP连接,设置MSS为1400
ip6tables -A OUTPUT -p tcp --tcp-flags SYN SYN -j TCPMSS --set-mss 1400
# 配置INPUT链:针对接收的IPv6 TCP连接,设置MSS为1400
ip6tables -A INPUT -p tcp --tcp-flags SYN SYN -j TCPMSS --set-mss 1400
# 配置FORWARD链:若本机作为网关,转发IPv6 TCP连接时生效
ip6tables -A FORWARD -p tcp --tcp-flags SYN SYN -j TCPMSS --set-mss 1400
验证效果
配置完规则后,立刻再次测试。
-
查看规则是否生效:
ip6tables -t mangle -L -v你应该能看到
TCPMSS set 1400的记录。 -
客户端再次访问:
curl -6 -v https://<域名>/这一次,
Client Hello之后,秒回Server Hello,证书交换顺利完成,网页内容成功加载!
持久化保存
确认没问题后,别忘了保存规则,否则重启服务器就白忙活了:
netfilter-persistent save
systemctl enable netfilter-persistent
五、总结
这次排查再次印证了网络工程中的一句名言:“It's always DNS or MTU.”(不是 DNS 问题就是 MTU 问题)。
在 IPv6 普及的今天,由于中间链路设施参差不齐,PMTU 黑洞问题变得更加普遍。如果你维护的 IPv6 站点出现“连接建立成功但传输卡死”的现象,请务必第一时间检查 MTU 问题。
通过 ip6tables 进行 MSS Clamping 是目前解决此类问题最简单、副作用最小的方案。它不需要你求爷爷告奶奶去让运营商放行 ICMP,只需在自己服务器上加一行命令,就能让你的网站在糟糕的网络环境下依然健步如飞。
希望这篇笔记能帮你少走弯路!
好文,我先试试我的服务器有没有这种情况
尝试了下,我的服务器没有这种情况,似乎是要中间路由器有限制的时候才会导致这种情况?我是否需要提前设置ip6tables规则呢
@seamee #1
应该不需要,我手头四十多台服务器也只有这个出了问题,服务器是NUBE的SJC中国优化,没有问题的话不用设置也可以正常跑,大部分商家默认设置就是可以的。
感谢分享
好文。我看到另一个方案:
主机端开启 PLPMTUD (无需 ICMP)
如果在主机端(服务器或个人电脑)遇到此问题,且无法修改中间网络设备,可以开启 Packetization Layer Path MTU Discovery (PLPMTUD, RFC 4821)。
这种机制不依赖 ICMP,而是通过 TCP 协议层自己试探(发送不同大小的包看是否有 ACK 回来)来测算 MTU。
注:虽然参数名里有 ipv4,但该内核参数对 IPv6 同样生效。
我在一个v6转发机上也遇到mtu导致速度降低的情况,调整后效果很好。
https://www.nodeseek.com/post-558611-1
软路由开启IPV6网络会卡,大多数也是这个问题
感谢分享
技术大拿
我也遇到类似问题了,但是我是在开了ufw之后才出现的,至今没排查解决完
错的不是我,是世界
毁灭吧
@mocifaith #0 好文标记