Lihaoya's blog

调用微信 API 时出现 TLS 握手超时可能是 MTU 问题

Published at July 24, 2025, last modified at July 24, 2025

最近在对接微信接口时,遇到一个很诡异的问题:

偶尔会报 TLS 握手超时,但在宿主机 curl 是正常的。 这类“偶发性 + 网络相关”的问题最难排查,一开始我陷入了“是不是 SDK 有 bug?”的误区,直到我真正深入底层,才发现——原来是 MTU 惹的祸。

环境

在此之前大部分的生产环境我都是部署在公网上,这一次是部署在内网的虚拟化 VM 上。 我的部署环境是这样的:

问题场景是这样的:应用偶尔报错TLS handshake timeout。不是所有请求都失败,只是偶尔出现,在宿主机执行 curl -v https://api.weixin.qq.com 正常,毫无问题。

我排查问题的思路

网络相关的超时问题,常见有几个方向:

  1. DNS 解析慢?
  2. 服务端网络抖动?
  3. SDK 实现问题?
  4. 网络丢包或 MTU 不匹配?

第一步:确认 DNS 没问题 我用 digcurl -w 分析 DNS 解析时间,DNS 时间非常短,都是 10ms 以下,所以可以排除 DNS 问题。

第二步:用 curl 模拟请求 宿主机直接 curl 没问题:curl -v https://api.weixin.qq.com TLS 握手也很快,毫无异常。 但问题是在容器中运行的服务出现超时。 那我就在容器内也跑一次 curl

curl -v https://api.weixin.qq.com

这时候就有趣了:

于是可以断定——问题只出现在容器内部。

关键点:MTU 不一致导致握手失败

接下来,我开始怀疑是否存在网络层的问题。 于是我通过 ip link show 查看了宿主机和容器内的 MTU 设置。以宿主机为例,结果如下:

# 在宿主机执行
ip link show

结果是这样的:

...
2: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1442 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
...

宿主机 MTU 只有 1442,而不是标准的 1500。 而 Docker 网络默认 MTU 是 1500 ,将 Docker 容器的 MTU 修改为 1442 后再次执行 curl 请求,TLS 握手变得稳定了。

为什么 MTU 会影响 TLS 握手?

TLS 握手初期会交换证书、加密参数等内容,其中证书包往往比较大。如果 MTU 设置过大,导致网络包超过了底层网络(比如 Docker overlay、VPN、bridge)实际支持的最大尺寸,就可能发生 IP 分片。

一旦需要分片,如果中间链路的防火墙丢弃分片,或者因 ICMP 被禁用而导致 PMTU(路径MTU发现)探测失败,数据包便会丢失,最终导致握手超时。这也解释了为什么问题是“偶尔”出现:并非所有TLS连接(例如会话复用的连接)的握手包都大到需要分片。

解决方案

确定原因后,解决方法就简单了:

  1. 把容器内的网卡 MTU 设置为 1442 或更小。
  2. 或者在 host 上配置,使 bridge 的 MTU 与物理网卡保持一致。
  3. 如果使用 Docker / Docker Compose,可以添加 --mtu 参数或在配置文件中指定:
    networks:
      default:
        driver: bridge
        driver_opts:
          com.docker.network.driver.mtu: "1442"
    

小结

这是一次非常典型的“网络层导致 TLS 握手失败”的问题,不是代码的问题,也不是 SDK 的锅。 从表面上看,是“偶发性超时”,但这次排查揭示了其背后的根源在于:

所以如果你也遇到 HTTPS 或 SDK 请求偶尔超时,一定要检查一下 MTU 设置,特别是在容器或桥接网络场景下。


Comments(1)

blackstorm blackstorm

此文档包含 AI 生成部分,如有纰漏请 @ 我

Leave a Comment