Docker Error Guide: 'net/http: TLS handshake timeout' Registry Pull Failures
Fix Docker 'net/http: TLS handshake timeout' on registry pulls: configure the dockerd proxy, lower docker0 MTU, fix IPv6 blackholes, and work around rate limits.
- #docker
- #troubleshooting
- #errors
- #registry
Exact Error Message
A docker pull or docker login hangs for ~30 seconds, then fails part-way through the registry handshake:
Error response from daemon: Get "https://registry-1.docker.io/v2/": net/http: TLS handshake timeout
The same error appears under any registry. With a slow connection you may see it only on larger images, after the manifest succeeds but blob downloads stall:
error pulling image configuration: download failed after attempts=6: net/http: TLS handshake timeout
The connection reached the registry’s IP and started TLS, but the handshake never completed in time.
What It Means
docker pull runs inside the daemon, which makes an HTTPS request to the registry. “TLS handshake timeout” means TCP connected (or appeared to), the client sent ClientHello, but the server’s response — the ServerHello and certificate — did not arrive within the deadline. This is distinct from a DNS failure (name didn’t resolve) or a connection refused (no listener): the path is open enough to start TLS but too slow, throttled, or fragmented to finish.
The usual culprits are network-path problems between the daemon and the registry: a corporate proxy the daemon doesn’t know about, an MTU mismatch that silently drops the large certificate packet, a blackholed IPv6 route Docker prefers, or registry-side throttling. Because the daemon — not your shell — makes the request, proxy and environment settings must be configured for the daemon process, not just your login shell.
The MTU case deserves special attention because it is the most counter-intuitive and the most commonly misdiagnosed. The TLS ServerHello plus certificate chain is often large enough to require a full-size packet. If docker0 (or an overlay/VPN interface in the path) advertises a 1500-byte MTU but the real path can only carry, say, 1400 bytes, that large packet is dropped — and if ICMP “fragmentation needed” messages are also filtered (common on locked-down networks), there is no feedback to shrink it. The result is a connection that completes the small initial handshake packets, then hangs forever on the certificate, producing exactly the “TLS handshake timeout” symptom. This is why the same registry can work from a laptop and fail from a VPN-connected build host with no DNS or firewall difference at all.
Common Causes
- Slow or flaky network to the registry, so the handshake exceeds the timeout.
- Corporate proxy / MITM not configured for dockerd — the daemon connects directly and is blocked or stalled.
- MTU mismatch on
docker0— over VPN/overlay the default 1500 MTU fragments the TLS cert packet, which gets dropped. - Registry overloaded or rate-limiting — Docker Hub throttling slows or stalls handshakes.
- IPv6 blackhole — the daemon resolves an AAAA record and tries an IPv6 path that silently drops.
- Firewall throttling 443 — a security appliance rate-limits or inspects TLS, delaying
ServerHello.
How to Reproduce the Error
On a host behind a proxy that hasn’t been configured for the daemon, simply pull:
sudo systemctl restart docker
docker pull alpine:latest
The daemon connects directly, the proxy blocks the path, and the handshake times out. To reproduce the MTU case, set an oversized MTU on docker0 over a VPN link with a smaller path MTU:
sudo ip link set dev docker0 mtu 1500
docker pull ubuntu:24.04 # large blobs stall on fragmented cert packets
Diagnostic Commands
First test the registry path directly with verbose TLS — this separates network from Docker config:
curl -v --max-time 20 https://registry-1.docker.io/v2/
time curl -so /dev/null https://registry-1.docker.io/v2/
If curl also stalls at SSL connection, it is the network/proxy/MTU. Check whether the daemon knows about a proxy and inspect its systemd drop-in:
docker info | grep -i proxy
sudo systemctl show docker --property=Environment
sudo cat /etc/systemd/system/docker.service.d/http-proxy.conf 2>/dev/null
Check the MTU on docker0 and the upstream interface, and test IPv6 vs IPv4 reachability:
ip link show docker0 | grep -o 'mtu [0-9]*'
ip link show | grep -o 'mtu [0-9]*'
curl -4 -v --max-time 15 https://registry-1.docker.io/v2/ # force IPv4
sudo journalctl -u docker --since "10 min ago" | grep -i 'tls\|timeout\|proxy'
Step-by-Step Resolution
-
Configure the proxy for the daemon, not just your shell. Create a systemd drop-in so
dockerduses it:sudo mkdir -p /etc/systemd/system/docker.service.d sudo tee /etc/systemd/system/docker.service.d/http-proxy.conf <<'EOF' [Service] Environment="HTTP_PROXY=http://proxy.example.com:8080" Environment="HTTPS_PROXY=http://proxy.example.com:8080" Environment="NO_PROXY=localhost,127.0.0.1,.internal" EOF sudo systemctl daemon-reload && sudo systemctl restart docker -
Lower the MTU when on VPN/overlay. Set Docker’s default bridge MTU in
/etc/docker/daemon.jsonto match the path:{ "mtu": 1400 }sudo systemctl restart docker -
Force IPv4 / fix IPv6. If
curl -4succeeds but the default stalls, disable the broken IPv6 route or prefer IPv4 for the registry path at the host level. -
Retry or use a mirror for transient Hub overload or rate limits. Configure a registry mirror in
daemon.json:{ "registry-mirrors": ["https://mirror.example.com"] } -
Trust the corporate MITM CA if an inspecting firewall presents its own cert — install it into the system trust store and restart Docker.
After each change, sudo systemctl restart docker (and daemon-reload for systemd drop-ins) and re-test with docker pull alpine:latest.
Work through these in the order your diagnostics pointed to rather than applying all at once. If curl -v to the registry succeeds quickly but docker pull still times out, the network is fine and the problem is Docker-specific — almost always the proxy drop-in (step 1), because curl reads your shell’s proxy env while the daemon does not. If curl itself stalls at the SSL stage, the path is broken and you are looking at MTU (step 2) or IPv6 (step 3); the quickest confirmation is whether curl -4 succeeds where the default hangs. Treat the registry mirror (step 4) as the durable fix for Hub rate limits even after the immediate timeout is resolved, since throttling will recur under load.
How to Prevent the Issue
- Configure
HTTP_PROXY/HTTPS_PROXY/NO_PROXYas a systemd drop-in fordockerdon every proxied host — shell env vars alone never reach the daemon. - Set a correct
mtuindaemon.jsonto match VPN/overlay path MTU so TLS certificate packets aren’t silently dropped. - Use a pull-through registry mirror to absorb Hub rate limits and outages.
- Pin and test IPv4 reachability to your registries; disable IPv6 on hosts where the v6 path is unreliable.
- Monitor pull latency and alert on repeated
TLS handshake timeoutlines injournalctl -u dockeras an early network-degradation signal.
Related Docker Errors
- Docker Error Guide: ‘Temporary failure in name resolution’ — rule out DNS first; a name that won’t resolve never reaches the TLS stage.
- Browse more Docker troubleshooting guides for networking, DNS, and daemon startup errors.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.