关于我们

质量为本、客户为根、勇于拼搏、务实创新

< 返回新闻公共列表

网页能打开但大文件传不动:隧道里的MTU和MSS到底出了什么问题

发布时间:2026-09-24

一台云主机 SSH 连得上,敲命令有回显;ping 网关通、ping 公网通;curl 首页能拿到 HTML,浏览器标签页上的标题也刷出来了。然后你 scp 一个 200MB 的日志包过去,进度条停在 0%,一动不动。换个 20KB 的配置文件再传,秒传成功。工单里对这类现象最常见的描述是"网络时好时坏",但它跟带宽、跟丢包率、跟线路质量一点关系都没有。(以下为典型故障场景描述,并非特指某一真实客户案例。)

  • 能连上 ≠ 能用。TCP 三次握手的包都只有几十字节,前几个业务包也不大,它们走得通不代表 1500 字节的满载包走得通。
  • 链路里多一层隧道,封装头就要吃掉几十字节的有效载荷空间;接口 MTU 不跟着降,每个大包都是注定被丢的。
  • PMTUD 靠 ICMP Type 3 Code 4 回传"需要分片"来让发送端降 MTU,这个报文一旦被防火墙或安全组丢掉,发送端永远不会知道该降——这就是黑洞。
  • 处置顺序不能反:先用 ping -M do -s 二分出真实路径 MTU,再决定是改隧道口 MTU 还是做 MSS 钳位。一上来把 MTU 改成 9000 是最典型的反向操作。
  • MSS 钳位只改 SYN 里的 MSS 值,只对 TCP 生效、只对新建连接生效,对 UDP(含 QUIC/HTTP3)无效——这条边界必须先想清楚再动手。

SSH 连得上、scp 卡在 0%:这是一类典型的"假连通"

把上面那个现象拆开看,会发现它有一个非常整齐的规律:凡是"小"的都通,凡是"大"的都不通。SSH 登录时的密钥交换报文不超过几百字节,登录成功;登录后敲一条 ls,返回的目录列表只有几百字节,正常显示;scp 传 20KB 文件,TCP 把它切成十几个包,每个包都在 MSS 以内,秒传;200MB 的文件要切成十几万个满载包,卡在第一批就再也不动了。

这类故障最迷惑人的地方在于,它几乎能骗过所有常规的网络健康检查。ping 默认只发 56 字节(部分发行版 64 字节)的载荷,加上 8 字节 ICMP 头和 20 字节 IP 头,整包不到 100 字节,当然通。mtr 默认也发小包,跳数和延迟都很漂亮。运维看到这一串绿油油的结果,第一反应是"网络没问题,是不是应用层的问题",然后开始在应用日志里翻,越翻越远。

我在工单里还有一个高频变体:网站能打开标题,页面内容永远加载一半。原因是 HTML 文档本身可能就 5KB,一次响应就回来了;而首屏那几张图、那个 300KB 的 JS bundle、那个被内联进来的字体文件,全都超过了路径能承载的单个报文大小。浏览器渲染出标题就停住,控制台里挂着几个 pending 的请求,等 30 秒超时后变成 failed。用户看到的现象是"网站坏了",实际的物理事实是"大于 1400 字节的包全被丢了"。

还有一个更隐蔽的版本:HTTPS 握手成功,但 body 传不完。TLS 握手阶段的 ClientHello、ServerHello、密钥交换报文普遍在几百字节量级,能过;一旦进入应用数据传输,HTTP 层开始下发大块数据,连接就挂了。反过来,如果服务端证书链特别长(多级中间 CA + OCSP 装订),握手阶段的 Certificate 报文本身就可能超过 1400 字节,这时连握手都完成不了,表现为"这个网站直接打不开"——而同机房、证书链短的站点却一切正常。

为什么"小的行大的不行":MSS、DF 位和 PMTUD 是怎么一起失效的

MSS 是双方在握手时就谈好的一个数

MTU(Maximum Transmission Unit)是二层一次能承载的最大 IP 报文,标准以太网是 1500 字节。MSS(Maximum Segment Size)是 TCP 层一次愿意接收的最大 payload,它在三次握手的 SYN 包里通过 TCP 选项通告给对端,双方各说各的,取较小值执行。

换算关系很干净:MSS = MTU − IP 头(20)− TCP 头(20)。所以 1500 的 MTU 对应 1460 的 MSS;如果开了 TCP 时间戳选项,TCP 头变成 32 字节,MSS 相应降到 1448。ss -i 里经常能看到 1448 这个数,不是配错了,是时间戳占掉了 12 字节。

关键在于:MSS 是主机在握手时根据自己的网卡 MTU 算出来的,它并不知道中间链路上有什么。一台网卡 MTU 1500 的机器,发出的 SYN 里就写着 MSS 1460。它对端会照着这个数发满载包,每个包 1500 字节。

DF 位和 PMTUD:本来有一套自愈机制

Linux 默认对 TCP 报文置 DF(Don't Fragment)位,也就是 net.ipv4.ip_no_pmtu_disc = 0,PMTUD 是开着的。置了 DF 的报文遇到一个 MTU 更小的出接口时,路由器不能分片,只能丢弃,然后应当回一个 ICMP Type 3 Code 4 —— Destination Unreachable / Fragmentation Needed and DF Set —— 报文里还带着下一跳的 MTU 值。发送端收到它,就知道这个目的地只能走 1400,于是把该目的地的 PMTU 记进路由缓存,后续报文按 1400 发,MSS 相应降到 1360。

这套机制跑得很顺的时候,你甚至感觉不到它的存在。问题出在"应当"两个字上。现实里大量网络出于安全考虑直接把 ICMP 一刀切掉:iptables -A INPUT -p icmp -j DROP、云上安全组默认只放行"全部 ICMP"或"全不放行"两个极端、运营商侧对 ICMP 限速、某些负载均衡设备不回 unreachable。于是那台 MTU 更小的路由器老老实实发了 ICMP need frag,报文却在回程路上被丢得干干净净。

发送端这边的状态是:发了满载包,没收到 ACK,重传,还是没 ACK,退避,再重传,一直重传到应用层超时。它从头到尾不知道自己该把包变小,因为它从来没收到过那个告诉它"你该变小"的报文。这就是 PMTUD 黑洞(PMTU black hole),也是"小的行大的不行"的全部物理原因。整条链路上没有任何设备在报错,没有任何一条日志说"包太大了",你只能从症状去反推。

这里有个非常实用的判读分水岭:当你用 ping -M do -s 1472 去打,如果 PMTUD 是通的,你会看到明确的回显,比如 Linux 上 From 10.0.0.1: icmp_seq=1 Frag needed and DF set (mtu = 1400),Windows 上是 Packet needs to be fragmented but DF set如果 PMTUD 被掐断了,你什么都看不到——就是纯超时,一行提示都没有。有没有那行 "Frag needed",直接告诉你这是"路径确实变窄了"还是"窄了但没人通知你",两种情况的处置力度完全不同。

隧道为什么是重灾区:封装头吃掉的那几十个字节

纯物理链路上的 PMTUD 黑洞其实不多见,因为运营商设备的 MTU 通常是一致的。真正高发的是链路上多了一层封装的场景:GRE、IPsec、VXLAN、WireGuard、OpenVPN、容器网络,全算。

道理很直白。假设物理口 MTU 是 1500,你在上面起一条 GRE 隧道。原始 TCP 报文 1500 字节进来,系统要给它套一个新的 IP 头(20)+ GRE 头(4),于是外层报文变成 1524 字节,超过物理口 MTU,直接发不出去。如果你的隧道口 MTU 也是 1500,这条隧道里的每一个满载包都是废包。

不同封装的开销是个需要背下来的表,排查时一查一个准:

  • PPPoE:8 字节。家庭宽带和部分拨号场景的老熟人,物理 MTU 变成 1492,MSS 1452。
  • IPIP:20 字节。最简封装,只加一个新 IP 头。
  • GRE:24 字节(新 IP 20 + GRE 4);带 Key 字段变 28,带 Key + Sequence 变 32。有效 MTU 1476,MSS 1436。
  • VXLAN:50 字节(外层以太 14 + 外层 IP 20 + UDP 8 + VXLAN 头 8)。有效 MTU 1450,MSS 1410。这是容器网络默认值降不下来的头号原因。
  • Geneve:约 58 字节,比 VXLAN 多出可变长选项字段,具体看 CNI 实现。
  • WireGuard:名义开销 32 字节(IPv4)/ 40 字节(IPv6),含外层 IP、UDP、4 字节头部和 16 字节认证标签,另有 16 字节对齐填充。所以它默认把接口 MTU 设成 1420,留了充分余量,对应 MSS 1380。
  • IPsec ESP 传输模式:约 36–56 字节,取决于算法。AES-GCM 只用 8 字节 IV、16 字节 ICV,且不按块填充,开销偏小;AES-CBC + HMAC-SHA 要 16 字节 IV、按 16 字节块填充(最多 15 字节)、再加 12–16 字节 ICV,开销明显更大。
  • IPsec ESP 隧道模式:传输模式基础上再加 20 字节新 IP 头,落在 56–77 字节区间。所以 IPsec 隧道的工程惯例是接口 MTU 取 1400、MSS 取 1360,这个数字就是这么来的。
  • GRE over IPsec 双重封装:叠加后 80–100 字节,隧道口 MTU 常常要压到 1350 附近才稳。

把这些数摆在一起,结论就很清楚了:每加一层封装,可用的 payload 就少一截,但隧道口的 MTU 如果你不去显式设置,很多实现会默认照抄物理口的 1500。这时候端到端发出来的每个满载包,都是"我看得见、链路上发不出去、丢了还无人通知"的死包。隧道本身是通的(隧道保活、隧道内的小包、隧道建立过程都没问题),所以监控看不出异常,业务却全线卡死。

还有一种更麻烦的情况:隧道两端 MTU 配得不一样。A 端隧道口 1400、B 端 1500,那么 B→A 方向会出问题,A→B 方向正常。这解释了为什么"我这台机器访问对端没事,对端访问我就卡"。路径 MTU 是有方向的,两个方向必须各测一次,这点在后面的定位步骤里会反复强调。

症状谱:六种常见表现和它们各自的一句话验证

把上面讲的机理落到工单语言上,下面这张对照表可以直接当排查手册的第一页用。左边是你听到的现象,右边是"敲哪条命令、看到什么就算确认"。

表现 最可能的原因 一条命令先验证 看到什么就能确认
ping 通、scp 小文件秒传、大文件永远 0% 路径 MTU 小于接口 MTU,且 PMTUD 黑洞 ping -M do -s 1472 目标IP 纯超时、没有任何 "Frag needed (mtu=xxxx)" 提示 = ICMP 被丢;有提示则 PMTUD 正常、只是路径真的窄
网页标题刷出来了,图片和 JS 加载一半 HTML 小响应能过,大资源报文超限被丢 curl -s -o /dev/null -w '%{size_download}\n' 大文件URL 下载字节数停在一个固定值不再增长,然后连接超时,而不是报 404/500 这类应用层错误
HTTPS 握手成功但 body 传不完;部分站点直接打不开 证书链长的站点握手报文本身就超限;TLS 记录大于路径 MTU tcpdump -ni any 'tcp port 443' -vv 同一个 seq 号反复出现 TCP Retransmission、对端无 ACK,且没有任何 ICMP 回包
数据库单条插入正常、批量插入卡死 单条报文小,批量 INSERT 的报文远超 MSS 另开窗口 ss -ti '( dport = :3306 )' mss 仍显示 1460 / pmtu 仍显示 1500,而实际路径只支持 1400;retrans 计数持续上涨
只有部分 CDN、部分站点有问题,另一些完全正常 去往不同目的地的路径不一致,MTU 瓶颈在不同跳 对正常与异常目标各做一次 tracepath 两者结尾的 Resume: pmtu xxxx 数值不同;异常目标那一侧明显更小
隧道建起来之后,原本正常的业务开始卡 封装头吃掉了 payload 空间,隧道口 MTU 未同步下调 ip link show 看隧道口,tracepath 目标 看路径 隧道口 MTU 仍是 1500,而 tracepath 报出 pmtu 1400/1450/1436 之类比 1500 小的数

先别急着改配置:用 ping 二分法把真实路径 MTU 测出来

定位这一步的顺序不能乱。很多人上来就改 MTU,改完发现有的业务好了有的更糟,然后又改回去,来回折腾几轮才想起来"我其实不知道真实路径 MTU 是多少"。先把数字测出来,所有后续决策才有依据。

Linux:ping -M do -s 的二分法

-M do 表示禁止分片(DF 置位),-s 指定的是 ICMP 载荷字节数,不含 ICMP 头 8 字节和 IP 头 20 字节。所以:

  • ping -M do -s 1472 目标 → 1472 + 8 + 20 = 1500,等价于测"1500 能不能过"。
  • 通了,说明到这个目标的路径 MTU ≥ 1500,本条路径不是瓶颈。
  • 不通,往下降。用二分法最快:1472 → 1372(对应 1400)→ 1322(对应 1350)→ 1272(对应 1300)。找到能通过的最大 -s 值,加 28 就是这条路径的 MTU
  • -c 2 -W 2 可以显著加快探测速度,不用等默认的一秒一发。

判读刚才说过了,再强调一次:"Frag needed and DF set (mtu = 1400)" 这行回显本身就是最值钱的信息——它既告诉了你 ICMP 是通的(PMTUD 没被掐),又直接给了你答案。看到它,你就可以跳过二分法,直接把 1400 记下来。反之如果只超时不回显,说明这条路是黑洞,你必须自己二分,并且后面大概率要靠 MSS 钳位或降接口 MTU 来治,光靠放行 ICMP 未必来得及。

macOS 上对应的写法是 ping -D -s 1472 目标(-D 置 DF)。

Windows:ping -f -l

Windows 的 ping-f 置 DF,-l 指定载荷大小,换算方式和 Linux 一样:ping -f -l 1472 目标 测的是 1500。返回 Packet needs to be fragmented but DF set 说明 ICMP 通、路径窄;返回 Request timed out 且小值能通,说明是黑洞。

tracepath 一行拿答案

tracepath 目标 是这套排查里性价比最高的命令,它一边 trace 一边直接把路径 MTU 探测结果打出来,典型输出长这样:

1?: [LOCALHOST] pmtu 1500
1: 10.0.0.1 0.417ms
2: 10.0.0.1 0.512ms pmtu 1400
3: 203.0.113.1 3.214ms
4: 198.51.100.7 5.118ms reached
    Resume: pmtu 1400 hops 4 back 4

第 2 跳那个 pmtu 1400 就是答案,结尾的 Resume: pmtu 1400 是最终结论。注意这一跳同时也是瓶颈所在的网络位置,如果你认得那个 IP 属于哪台设备(防火墙?隧道网关?某条运营商链路?),处置方案基本就出来了。Windows 上没有 tracepath,可以用 ping -f -l 手二分,或者装 tracetcp。

服务端这一侧要看什么:接口 MTU、路由 MTU 和实际协商的 MSS

路径 MTU 测出来之后,回到本机核对几个数,看看它们之间是不是对齐的。

接口 MTU

ip link show(或老一点的 ifconfignetstat -i)会列出每个接口的 mtu。重点看三层:物理口、隧道口、容器网桥口。典型的问题配置是 eth0 mtu 1500、gre1 mtu 1500、docker0 mtu 1500,而物理路径实际只支持 1400——三个数全是"看起来很标准"的 1500,没有任何一个显式暴露问题。

顺手把 ip -d link show 隧道口 也看一眼,它能告诉你这是 GRE、IPIP、VXLAN 还是别的,从而对上前面那张封装开销表。

路由 MTU

ip route show 的输出里,如果某条路由显式带了 mtu,那它优先于接口 MTU 生效。隧道场景下常见做法是给隧道路由单独打上 mtu:ip route change 10.8.0.0/24 dev gre1 mtu 1400。如果你发现接口 MTU 是 1500 但路由上挂着一个更小的 mtu,那说明有人已经处理过,问题可能出在另一半路径上。

内核为某个目的地学到的 PMTU 也会体现在这里,部分内核下 ip route get 目标 会带出缓存的 pmtu 值。这个值如果和实测不符(比如还是 1500),说明发送端的 PMTU 缓存是过期的,那正是黑洞的证据。

实际协商的 MSS

ss -i(建议 ss -ti)能看到每条 TCP 连接的内部状态,其中这几个字段是关键:

ESTAB 0 0 10.0.0.5:ssh 198.51.100.7:52344
    cubic wscale:7,7 rto:204 rtt:1.8/0.9 mss:1460 pmtu:1500 rcvmss:536 advmss:1460 cwnd:10 ssthresh:8 retrans:0/12

  • mss:这条连接实际在用(或通告)的 MSS。如果它是 1460 而路径只支持 1400,问题坐实。
  • advmss:本端通告出去的 MSS,改 MSS 钳位后这里会变。
  • pmtu:内核认为的到这个目的地的路径 MTU。它是 1500 而实际 1400,就是典型的黑洞状态。
  • retrans:0/12:重传计数在涨,说明数据在发、ACK 不回——配合大包才有问题这个前提,基本可以定性。

再用抓包拿一份铁证:ICMP need frag 到底有没有回来

接口、路由、MSS 这三个数都看过了,如果你想拿到铁证,就抓包。三步:

先看 ICMP need frag 有没有回。

tcpdump -ni any 'icmp[0] == 3 and icmp[1] == 4'

这个过滤条件精确匹配 ICMP Type 3 Code 4。发起一个大文件传输的同时跑这条命令,如果一个包都抓不到,而传输确实卡住了,那"PMTUD 被掐断"就是板上钉钉。如果能抓到,包里还会带上下一跳的 MTU 值,直接读出来用。别用 -ni any 抓错接口,隧道场景建议同时抓物理口和隧道口,对比"外层有没有收到、内层有没有透传"。

再看 SYN 里的 MSS 协商值。

tcpdump -ni any 'tcp[tcpflags] & tcp-syn != 0' -vv

抓下来会看到 mss 1460,sackOK,TS val ... 这样的选项串。如果你已经配了 MSS 钳位,这里应该看到被改写后的值(比如 1360);如果还是 1460,说明钳位规则没命中这条流——链路上挂错位置、只挂了单向、或者规则顺序被前面的 ACCEPT 抢先了,都得回头查。

最后看重传形态,把黑洞和拥塞区分开。

PMTUD 黑洞的典型抓包形态非常有辨识度:发送端反复重传同一个 seq 的大包,对端一个 ACK 都没有,也没有 dup ACK,更没有 window full。这和拥塞完全不同——拥塞时你能看到 dup ACK、快速重传、cwnd 收缩、RTT 抖动;黑洞时一切安静,只有同一段数据在原地打转。如果你在 ss -i 里看到 receive window 变成 0,那是另一类问题(应用层不读数据),别混为一谈。

四种处置手段,各自的适用边界在哪

到这一步,你已经知道真实路径 MTU(比如 1400)和该有的 MSS(1360)。下面四条路怎么选,取决于你能动到哪一层。

一、改隧道接口 MTU:治本,但要求端到端一致

ip link set dev gre1 mtu 1400(Windows 是 netsh interface ipv4 set subinterface "以太网" mtu=1400 store=persistent)。这是最干净的做法:隧道口 MTU 降下来,上层 TCP 自动按 MSS 1360 协商,TCP 和 UDP 一起解决,不用管防火墙。

代价是影响面大且必须两端对齐。A 端改成 1400、B 端还是 1500,会出现单向异常,比原来更难查。而且这不是临时生效就算完——ip link set 重启即失效,必须写进网络配置(netplan 的 mtu:、NetworkManager 的 802-3-ethernet.mtu/etc/network/interfacesmtu 1400、systemd-networkd 的 [Link] MTUBytes=)。隧道重建、机器重启之后能不能保持,一定要验证。

还有一层影响要算进去:改接口 MTU 会波及所有走这个接口的流量,包括那些本来没问题的目的地。如果你的隧道只服务于特定网段,用路由 MTU(ip route ... mtu 1400)范围更小,优先于改接口。

二、TCP MSS 钳位:最常用,影响面可控

原理是在 SYN 包经过防火墙时,改写 TCP 选项里的 MSS 值,让双方从建连那一刻起就用小 MSS 发包,压根不产生超限的大包。它只在三次握手时动一次 SYN,连接建立后不再插手,性能开销可以忽略。

iptables 写法:

iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1360

nftables 写法:

nft add rule inet filter forward tcp flags syn tcp option maxseg size set 1360

两条命令的差别值得说清楚。--clamp-mss-to-pmtu 是按"出接口路由的 MTU"来算的,它不会自己去探测路径 MTU。如果你的隧道口 MTU 还挂着 1500,clamp 算出来还是 1460,等于什么都没做——这是 --clamp-mss-to-pmtu 最常见的"配了没效果"的原因。所以要么先把出接口/路由的 MTU 改对再 clamp,要么直接用 --set-mss 1360 写死数字。排查阶段我更倾向写死数字:结果确定、可验证、不依赖别处的配置。

MSS 钳位的边界,三条必须记住:

  • 只对 TCP 有效。UDP 没有 MSS 概念,钳位对 DNS 大响应、NFS、视频流、QUIC 一概无效,这些只能靠降接口 MTU 或应用层分片。
  • 只对新建连接有效。已建立的存量连接不会改,配完要重新建连才看得到效果。
  • 方向要配对。钳位改写的是经过这台设备的 SYN。要管出向就在 FORWARD/OUTPUT 上做,要管入向就得在 PREROUTING 或对端设备上做,只挂一条链常常只解决一半业务。

改完记得持久化:iptables-save > /etc/iptables/rules.v4netfilter-persistent save,nftables 是 nft list ruleset > /etc/nftables.conf

三、允许分片 / 关掉 DF:能通,但不推荐

理论上,把 DF 去掉,让中间路由器分片,大包就能过去了。代价很实在:分片后的包在多数防火墙和 NAT 设备上会被丢弃或严重降级(很多设备不重组后续分片),分片导致吞吐下降、CPU 上升,而且分片本身还带来分片攻击的攻击面。这条路只在应急、且你能确认两端设备都能正常处理分片时短期用一下,不该作为长期方案。

四、黑洞探测 tcp_mtu_probing:兜底,别当首选

Linux 内核有一套主动探测机制:

sysctl -w net.ipv4.tcp_mtu_probing=1

取值含义:0 关闭(多数发行版默认)、1 检测到疑似黑洞时才启用、2 始终启用。相关参数还有 net.ipv4.tcp_base_mss(探测起始 MSS,默认 512)和 net.ipv4.tcp_probe_threshold(默认 8,连续多少次失败后启动探测)。

它的价值是"在你完全管不了中间网络时的最后一道防线"——比如客户端散布在各地、你只能改自己这台服务器。但它有探测成本和延迟,稳态下不会主动用满带宽,所以正确的定位是兜底而不是主力。能改 MTU 就改 MTU,能做钳位就做钳位,探测留给确实够不着的地方。

顺带一提,QUIC / HTTP3 走的是 UDP,不受 MSS 钳位影响,但它有自己的 DPLPMTUD(RFC 8899)机制,不依赖 ICMP,靠主动发探测包来发现 MTU。所以同一个黑洞网络里,HTTP/3 站点往往比 HTTP/1.1 站点表现更好——如果你观察到"只有某几个站点正常、而且是支持 HTTP/3 的那几个",这条能帮你快速佐证判断。

为什么不要一上来就把 MTU 改成 9000

"反正包大一点效率更高,直接上 jumbo frame 呗"——这是 MTU 话题里排名第一的反向操作。原因很简单但很硬:

巨型帧要求从源到目的整条二层路径上每一台设备都支持并启用 9000+ 的 MTU。交换机、路由器、网卡、虚拟交换机、防火墙、负载均衡、隧道端点,任何一跳不支持,超长帧就在那一跳被静默丢弃——注意是二层直接丢,连 ICMP 都不会有,因为以太网层根本没有"分片"和"不可达"这套机制。你把一个本来只是"部分业务卡"的问题,升级成"整个网段不通"。

更现实的是,公网路径上几乎不存在 9000 的 MTU。PPPoE 是 1492,大量运营商接入网是 1500,云厂商的 VPC 内部有些支持 9001 但一出 VPC 就打回 1500。你要真用上 jumbo,适用范围基本只有"同一个二层域内的服务器之间互访",比如存储网、计算集群的内部通信。跨机房、跨公网、跨隧道的场景,一律不要碰。

而且在隧道场景里,jumbo 根本不解决那个真问题:封装头吃掉的字节数是固定的,外层报文最终还是要走 1500 的物理链路。你把隧道口设成 9000,外层变成 9050,在物理口上一样发不出去。隧道口的 MTU 只能小于等于"物理路径 MTU − 封装开销",往大了改没有任何意义。

改完之后怎么验证,以及生产环境动这两项的几条硬规矩

改完不等于好了,必须验证,而且要用真实业务验证,不能只看 ping。

验证要做四件事

  • 复测路径 MTU。ping -M do -s <MTU-28> 目标 应该稳定通,上一个台阶(-s 加 20)应该按预期失败。两个方向各测一遍。
  • 确认 MSS 真的变了。重新建一条连接,ss -ti 里 mss / advmss 应该是钳位后的值,pmtu 也应该对得上。
  • 用真实业务压一遍。scp 一个 200MB 文件、curl 那个之前加载一半的页面、跑一次批量入库。只看 ping 是不够的——ping 通而业务不通,正是我们一开始要解决的问题。
  • 看一段时间。MTU 类问题常有滞后性:小包业务立刻恢复,但某个长连接、某个定时任务可能要等它下次重建才生效。观察窗口至少覆盖一个完整业务周期。

生产环境的几条硬规矩

  • 非高峰窗口动手。改接口 MTU 会瞬时影响该接口上的全部流量,改 MSS 钳位虽然温和,但规则加载瞬间也可能打断匹配。别在业务峰值做。
  • 改之前先记录原值和生效方式。MTU 改了重启失效、iptables 规则没保存重启丢失,这两件事在生产上出过太多次——改完"好了",重启之后"又坏了",然后被当成灵异事件。
  • 一次只改一个变量。别同时改 MTU 又加钳位又开探测,出问题你不知道是哪个生效了、哪个在捣乱。
  • 端到端要打招呼。隧道两端、容器网络和宿主机、客户端和服务端,任何一侧没同步,都会留下单向故障。
  • 放行 ICMP Type 3 Code 4 而不是放行整个 ICMP。这是治本的一环,安全上也可以接受:在 -p icmp -j DROP 之前加一条 iptables -A INPUT -p icmp --icmp-type fragmentation-needed -j ACCEPT,云上安全组同理,只放这一个类型。别为了修 MTU 把整个 ICMP 敞开。
  • IPv6 另算一套。IPv6 靠 ICMPv6 Type 2(Packet Too Big),中间路由器不做分片,对 PTB 的依赖更强;链路 MTU 不能低于 1280,隧道封装后一旦跌破这个值,包就没救了。

如果你自己没有那么多窗口去试错,也可以把环境交给更熟悉的人。一万网络深耕 IDC 19 年(成立于 2007 年),深圳南山总部,自营机柜最快 1 分钟上架,提供裸金属、GPU 定制与一万云弹性云(¥25 起,以官网实时价为准)等多种形态,工程师可以协助核对宿主机—隧道—容器三层的 MTU 设置、帮忙看安全组是否误拦了 ICMP need frag,这类配置层面的排查本来就是服务商该做的活。真遇到跨网段、跨地域的隧道,BGP 多线与 CN2 GIA 回国链路上的 MTU 一致性,也需要有人从两端同时确认。

排查现场被问得最多的八个问题

为什么 ping 通,网页却打不开?

因为 ping 默认只发 56 字节载荷,整包不到 100 字节,它验证的是"小包能不能到",不是"网络能不能用"。网页要传 HTML、JS、图片、字体,稍大的资源单个报文就上千字节。你要复现真实情况,得用 ping -M do -s 1472 去测满载包。另一个可能是 DNS:域名解析走 UDP,大响应(开了 DNSSEC 的 TXT 记录)也会撞 MTU,表现为"域名解析有时候慢、有时候失败"。两者可以用 dig +bufsize=1400 区分。

云主机的安全组会不会把 ICMP 拦掉,从而搞坏 PMTUD?

会,而且非常常见。很多平台的安全组默认模板只给"放行全部 ICMP"或"全不放行"两个选项,选后者就等于掐断 PMTUD。更隐蔽的是"只放行 echo request/reply"这类细化模板——它让 ping 看起来正常,却把 Type 3 Code 4 挡在外面,故障更难察觉。正确做法是单独放行 ICMP Type 3 Code 4,其他类型继续拒绝。宿主机上的 iptables -p icmp -j DROP 同样致命,规则顺序上要保证 need frag 的 ACCEPT 在 DROP 之前。

改了 MTU 要不要重启机器或者重启网络?

ip link set dev eth0 mtu 1400 是立即生效、不用重启,但它不会持久化——重启网络服务或重启机器就会回到原值。要持久化就得写进配置文件:netplan 的 mtu:、NetworkManager 连接的 802-3-ethernet.mtu、interfaces 文件的 mtu 1400、systemd-networkd 的 MTUBytes=,各发行版写法不一样。改接口 MTU 会让该接口短暂中断,属于会影响业务的动作;MSS 钳位规则加载则基本无感,但同样要 iptables-save 或写进 nftables 配置。

MSS 钳位会不会影响性能?

几乎没有。它只在三次握手时改写一次 SYN 里的选项值,连接建立后就不再参与转发处理,不像 NAT 或深度包检测那样逐包开销。真正影响吞吐的是 MSS 值本身变小带来的额外包头占比:MSS 从 1460 降到 1360,有效载荷占比大概从 97% 降到 96%,在千兆以内完全感知不到。相比"大包全丢、连接挂死",这点损耗可以忽略。真正要注意的不是性能,是它只对 TCP、只对新建连接生效这两条边界。

容器网络里到底该改哪一层?

三层都要看,但改的顺序是自上而下想清楚、自下而上动手。宿主机物理口/Underlay 的 MTU 是天花板,容器网桥和 veth 必须小于等于它减去封装开销。Docker 默认 docker0 是 1500,如果宿主机跑在 MTU 1450 的 VPC 里,容器就会中招——在 /etc/docker/daemon.json 里写 {"mtu": 1450} 然后重启 docker,注意已有容器要重建才生效。Kubernetes 更麻烦:Calico 的 VXLAN 模式要按 CALICO_IPV4POOL_MTU(或 ConfigMap 里的 veth_mtu)设成 Underlay−50,IPIP 模式减 20;Flannel 在 net-conf.json 里配 "MTU";Cilium 会自动探测但也可以显式指定。最容易漏的是 Pod 网络和宿主机网络走的是不同路径,两边要各测一次 tracepath。

为什么只有一部分网站有问题?

三个原因叠加。一是去往不同目的地的路径不同,MTU 瓶颈可能在不同跳上,所以 A 站正常 B 站卡死——用 tracepath 分别测两个目的地,看到的 pmtu 往往不一样。二是不同站点的资源分片策略不同,图片切得碎的站点更容易"部分加载",单个大 bundle 的站点直接卡死。三是协议差异:支持 HTTP/3 的站点走 QUIC,靠 DPLPMTUD 自己探测,不依赖 ICMP,所以在同一个黑洞网络里反而能正常打开。这三条能解释绝大部分"选择性故障"。

把 MTU 降下来会不会让带宽变差、传输变慢?

不会让带宽变差。带宽是链路速率,和 MTU 是两个维度。MTU 变小意味着每个包的有效载荷占比略降,同样的数据要多发几个包,包速率和中断/CPU 开销略增。从 1500 降到 1400,理论开销增量在 1% 量级,实际测不出来。真正会让你变慢的是现在这个状态——大包全丢、不停重传、连接挂起,那才是零吞吐。用一句话讲:降 MTU 是"每个包少装 100 字节",不降是"大包全丢",这两个损失根本不在一个数量级。

已经做了 MSS 钳位,为什么还有业务卡?

按顺序查四件事。第一,钳位是不是真的命中了——tcpdump 抓 SYN 看 mss 选项有没有被改写,没改就是链挂错(该挂 FORWARD 挂成了 OUTPUT)或顺序被前面的规则抢走了。第二,是不是只做了单向——出向钳了、入向没钳,服务端下发大响应依然超限。第三,是不是存量长连接没重建——钳位只对新建连接生效,跑了几天的连接池还是老 MSS。第四,这个业务是不是根本不走 TCP——UDP、QUIC、SCTP 全都不吃 MSS 钳位,得靠降接口 MTU 或应用层处理。第四条最容易漏,尤其是上了 HTTP/3 之后。

这些参数和口径我是从哪儿核的

本文涉及的封装开销(GRE 24、VXLAN 50、WireGuard 名义 32、ESP 传输模式 36–56、隧道模式再加 20 个新 IP 头)、MSS 换算关系、PMTUD 所依赖的 ICMP Type 3 Code 4、以及 Linux tcp_mtu_probing / tcp_base_mss / tcp_probe_threshold 等内核参数含义,均来自公开的协议标准与内核文档口径,命令与参数均为可直接执行的真实写法,未做任何虚构的实测或跑分。文中提到的一万网络相关服务能力、节点与价目(如一万云 ¥25 起、自营机柜最快 1 分钟上架、7×24 中文工单平均 5 分钟响应、硬件故障 10 分钟内自动迁移、免费系统盘每日 3 份快照、5–20G 免费防护、BGP 多线 + CN2 GIA 回国),以 https://www.idc10000.net/ 相应页面明示内容为准;涉及配置与报价的部分,具体以签约时最新报价与合同为准。文中场景为典型部署思路与故障推演,并非特指某一真实客户。

说到底:先测路径 MTU,再谈怎么改

这类故障之所以难查,不是因为机理复杂,而是因为它骗过了所有常规健康检查,且全程不产生任何一条像样的错误日志。你只要记住一件事:连通性测试用小包,业务用大包,两者不是一回事。遇到"能连不能用",第一反应不该是加带宽、换线路、重启服务,而是敲一条 ping -M do -s 1472,或者干脆 tracepath 一次,把真实路径 MTU 这个数拿到手。

拿到数之后的选择就简单了:能改到隧道两端就降接口 MTU,只能动中间设备就做 MSS 钳位,两头都够不着就开 tcp_mtu_probing 兜底,同时把 ICMP Type 3 Code 4 单独放行。唯独把 MTU 往 9000 改这条,任何时候都别走——它不是解法,是把问题放大。至于 UDP 那一侧(QUIC、DNS、实时流),记住 MSS 钳位管不着它,要么降接口 MTU,要么让应用层自己分片。


上一篇:2026 Nacos注册中心服务器租用选型手册:集群节点/磁盘/网络配置与避雷干货

下一篇:2026 SkyWalking链路追踪服务器租用部署攻略:采样率/存储/ES资源对比实测