Docker 容器 HTTPS 请求超时(TLS handshake timeout),原来是 MTU 在捣乱
前言
最近遇到一个很头疼的问题:
-
在 Docker 容器里用 Go/Python/Node 等发起 HTTPS POST 请求,总是卡死,报错:
net/http: TLS handshake timeout -
但奇怪的是:从容器里
ping目标域名/IP 是通的,而且延迟也很正常 -
用
curl -v https://xxx测试,也卡在 TLS 握手阶段,半天出不来结果
乍一看像网络不通,但 ping 又没问题,DNS 也解析正常,简直让人怀疑人生。
真相其实很简单:MTU 不匹配
ping 通 ≠ HTTPS 通
- ping 发的是很小的 ICMP 包(通常几十字节)
- TLS 握手要交换证书、密钥等信息,一个 ClientHello + ServerHello + Certificate 链加起来轻松几百到一千多字节
当网络路径上存在 MTU 不一致 的时候,小包能过,大包就会被丢弃或分片失败,尤其当中间路由器把 “不允许分片” 的包直接丢掉时(这就是经典的黑洞路由),就表现为:握手超时。
我们当时的环境对比:
- 容器里面的网卡(eth0):MTU = 1500(Docker bridge 默认值)
- 宿主机的外网网卡(ens3):MTU = 1450(云厂商常见设置,比如某些香港/国内的云主机、OpenStack 等)
结果就是:容器发出去的大包,到宿主机网卡这里超出了 1450,被直接丢了 → TLS 握手永远完成不了。
怎么快速确认是不是 MTU 问题?
进容器执行下面这条命令(把 目标换成你实际访问的 IP 或域名):
# 测试能否发 1500 字节的包(1472 + 28字节 ICMP/IP 头)
ping -M do -s 1472 www.google.com
- 如果报
packet too big/Frag needed/ 直接不通 → 说明路径 MTU < 1500 - 再逐步把 1472 往下降,比如 1420、1372、1320…… 直到能 ping 通
- 能通的最大值 + 28 ≈ 当前路径实际 MTU
我们测到大概在 1420 左右就能通,说明路径 MTU 很可能就是 1450 左右。
最终的解决办法(最简单有效)
编辑 Docker 的全局配置文件:
sudo vim /etc/docker/daemon.json
加入(或修改成):
{
"mtu": 1400
}
或者更保守一点:
{
"mtu": 1300
}
然后重启 Docker:
sudo systemctl daemon-reload
sudo systemctl restart docker
注意:重启 Docker 会让正在运行的容器全部停止,需要重新 docker-compose up 或 docker run。
改完之后,新建的容器 MTU 就会自动变成 1400/1300,问题瞬间解决,HTTPS 请求秒通。
其他可选的解决方式(看场景选)
-
只改某个网络的 MTU(推荐 docker-compose 项目)
networks: default: driver: bridge driver_opts: com.docker.network.driver.mtu: 1400 -
单个容器启动时指定
docker run --mtu=1400 ... -
宿主机网卡改回 1500(不推荐)
云厂商很多默认就是 1450/1400,强行改回去可能会导致宿主机本身的外网访问出问题。
总结
容器 HTTPS 握手超时 + ping 通 → 十有八九是 MTU 惹的祸
尤其是用云主机、自建 K8s、OpenStack、VPN、Overlay 网络的时候,优先检查宿主机 MTU 和 Docker 默认 1500 是否匹配。
下次再遇到类似诡异网络问题,别急着怀疑代码,先比对一下 ip link show 的 MTU 值,往往能省很多时间。

浙公网安备 33010602011771号