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 updocker run

改完之后,新建的容器 MTU 就会自动变成 1400/1300,问题瞬间解决,HTTPS 请求秒通。

其他可选的解决方式(看场景选)

  1. 只改某个网络的 MTU(推荐 docker-compose 项目)

    networks:
      default:
        driver: bridge
        driver_opts:
          com.docker.network.driver.mtu: 1400
    
  2. 单个容器启动时指定

    docker run --mtu=1400 ...
    
  3. 宿主机网卡改回 1500(不推荐)

    云厂商很多默认就是 1450/1400,强行改回去可能会导致宿主机本身的外网访问出问题。

总结

容器 HTTPS 握手超时 + ping 通 → 十有八九是 MTU 惹的祸
尤其是用云主机、自建 K8s、OpenStack、VPN、Overlay 网络的时候,优先检查宿主机 MTU 和 Docker 默认 1500 是否匹配。

下次再遇到类似诡异网络问题,别急着怀疑代码,先比对一下 ip link show 的 MTU 值,往往能省很多时间。

posted @ 2026-02-07 15:30  牛奔  阅读(95)  评论(0)    收藏  举报