Platforms and DevOps

Docker "net/http: TLS handshake timeout": causes and fixes

By · Last verified

Error messagenet/http: TLS handshake timeout

net/http: TLS handshake timeout means Docker reached the registry over TCP but the TLS handshake did not finish within 10 seconds. It is a network problem, not a certificate problem. Usual causes: a slow route, a daemon without the proxy your shell uses, an MTU problem that drops large packets, or a dead registry mirror.

What the error means

The Docker daemon gives up when a registry handshake takes longer than 10 seconds. The message comes from Go's net/http package, which Docker, containerd, kubectl and Helm are built on. A Go http.Transport gives up when a TLS handshake takes longer than its TLSHandshakeTimeout; the Docker daemon's registry transport sets it to 10 seconds (and 30 seconds for the TCP connect). You typically see:

Error response from daemon: Get "https://registry-1.docker.io/v2/": net/http: TLS handshake timeout

The important detail: docker pull asks the daemon to download the image. The daemon reads its proxy settings from its own environment (http.ProxyFromEnvironment in the daemon process). Variables exported in your terminal do not affect it.

Causes, most common first

1. A slow, congested or lossy path to the registry

Long-distance links, overloaded Wi-Fi, and networks with heavy packet loss stretch the few round trips of a handshake past 10 seconds.

  • Confirm: time the handshake from the same host:
curl -sS -o /dev/null -w 'connect=%{time_connect}s tls=%{time_appconnect}s http=%{http_code}\n' https://registry-1.docker.io/v2/

A healthy result returns HTTP 401 (authentication required) with tls well under a second. Several seconds, or a hang, reproduces the problem outside Docker.

  • Fix: use a closer registry mirror or a pull-through cache, or move the build to a network with a better route.

2. The daemon needs a proxy and does not have one

Corporate and some cloud networks only allow outbound HTTPS through a proxy. Your shell has HTTPS_PROXY, the daemon does not.

  • Confirm:
docker info | grep -iE "http proxy|https proxy|no proxy"
sudo systemctl show --property=Environment docker

3. An MTU black hole

On VPNs, PPPoE links and some cloud networks the path MTU is below 1500, and ICMP "fragmentation needed" messages are blocked. The small ClientHello gets through, the server's large certificate flight does not, and the handshake stalls.

  • Confirm: ip link show for the interface MTU, then ping -M do -s 1472 registry-1.docker.io (Linux). "Message too long" or silence while smaller sizes work points to MTU.
  • Fix: set the interface MTU to what the path supports, or enable MSS clamping on the VPN or router. If builds inside containers are affected, set "mtu" in daemon.json for the default bridge.

4. A configured registry mirror is slow or gone

registry-mirrors entries that no longer respond produce timeouts against the mirror's URL.

  • Confirm: docker info | grep -A3 "Registry Mirrors", then time a request to each mirror with the curl command above.
  • Fix: remove dead mirrors and restart the daemon.

5. A firewall or inspection device stalls TLS

Some devices hold TLS sessions for inspection or throttle specific destinations. If the curl test is fast from another network and slow only here, ask the network team. Devices that also re-sign traffic cause certificate errors instead, see corporate SSL inspection.

Fix commands by platform

Most fixes configure the Docker daemon itself, because it does not inherit the proxy settings of your shell.

Linux, daemon proxy via daemon.json (the method Docker recommends):

{
  "proxies": {
    "http-proxy": "http://proxy.example.com:3128",
    "https-proxy": "http://proxy.example.com:3128",
    "no-proxy": "localhost,127.0.0.1,.example.com"
  }
}
sudo systemctl restart docker

Linux, systemd drop-in (/etc/systemd/system/docker.service.d/http-proxy.conf):

[Service]
Environment="HTTPS_PROXY=http://proxy.example.com:3128"
Environment="NO_PROXY=localhost,127.0.0.1"
sudo systemctl daemon-reload && sudo systemctl restart docker

Registry mirror for Docker Hub images (/etc/docker/daemon.json):

{ "registry-mirrors": ["https://mirror.example.com"] }

Docker Desktop (Windows and macOS): Settings → Resources → Proxies (it can follow the system proxy), and Settings → Docker Engine to edit daemon.json for mirrors.

Kubernetes nodes with containerd: the same Go error appears in kubelet events; set the proxy in the containerd service environment rather than Docker's.

Don't disable certificate verification

Adding registries to insecure-registries, or disabling TLS checks in a mirror or proxy to "speed things up", does not fix a timeout and removes the integrity protection for images. Images pulled over an unverified connection can be swapped for malicious ones by anyone on the path.

Safer alternatives: fix the daemon proxy, use a trustworthy mirror over HTTPS, correct the MTU, and if a proxy re-signs TLS, add its root certificate to /etc/docker/certs.d/<registry>/ca.crt or the system trust store.

Verify the fix

docker info | grep -iE "proxy|mirrors" -A1
time docker pull hello-world

docker info should show the proxy and mirrors you configured, and the pull should finish without the timeout. Repeat the curl timing command; a TLS time under a second means the path is healthy. For how the handshake proceeds and where large packets appear, see the TLS handshake.

FAQ

Why does curl work but docker pull time out?

Your shell and the Docker daemon are different processes. curl uses the HTTPS_PROXY in your shell; the daemon is started by systemd or Docker Desktop and only sees the proxy configured in daemon.json, a systemd drop-in or Docker Desktop's settings. Check with docker info | grep -i proxy.

Can I raise the 10-second timeout?

Not through a documented daemon option; the value is set in the registry transport code. Fix the path instead: a healthy connection completes a TLS handshake in well under a second. If the link is genuinely slow, a nearby registry mirror or pull-through cache is the practical answer.

Will adding the registry to insecure-registries help?

No. insecure-registries allows plain HTTP or untrusted certificates for a registry; it does not make a slow handshake faster, and for public registries it only removes protection against tampered images. Use it only for a private test registry you control.

Does a registry mirror work for ghcr.io, quay.io or private registries?

No. Docker's documentation states that only the central Docker Hub can be mirrored with registry-mirrors. Images from other registries still connect directly, so their handshake problems need the proxy or network fixes.

Sources

  1. Docker docs: Daemon proxy configuration
  2. Docker docs: Registry mirror (registry-mirrors)
  3. Docker docs: dockerd reference (daemon.json keys, --mtu)
  4. Go docs: net/http Transport (TLSHandshakeTimeout)
  5. moby source: daemon/pkg/registry/registry.go (transport timeouts)