How to fix net/http: TLS handshake timeout in Docker
The 'net/http: TLS handshake timeout' error occurs when Docker cannot complete a TLS handshake with a registry within the allowed time. It is usually caused by MTU mismatches, proxy misconfiguration, firewall/VPN packet inspection, or network latency rather than the registry being down.
Quick fix
Fixes net/http: TLS handshake timeout
# Test TLS connection to Docker Hub
curl -v https://registry-1.docker.io/v2/
# Test DNS resolution
nslookup registry-1.docker.io
# Check if you can reach the auth server
curl -v https://auth.docker.io/- Verify network connectivity to Docker Hub
- Adjust MTU size to fix packet fragmentation
- Configure Docker daemon proxy settings
- Check the host clock for time skew
# Test TLS connection to Docker Hubcurl -v https://registry-1.docker.io/v2/# Test DNS resolutionnslookup registry-1.docker.io# Check if you can reach the auth servercurl -v https://auth.docker.io/net/http: TLS handshake timeout
What this error means
The "net/http: TLS handshake timeout" error occurs during the initial security handshake phase when Docker tries to establish an encrypted connection to a container registry like Docker Hub (registry-1.docker.io). The TLS handshake is the process where your Docker client and the registry server exchange cryptographic keys and certificates to establish a secure, encrypted connection. When this handshake takes longer than the timeout period (typically 10-15 seconds), Docker aborts the connection and displays this error. Unlike a connection refused error where the server actively rejects the connection, a TLS handshake timeout means the TCP connection was established but the encrypted negotiation could not finish in time. This points to packets being delayed, dropped, or partially blocked somewhere in the network path between Docker and the registry. It is common in corporate networks with deep packet inspection, on VPN connections, or when there are MTU (Maximum Transmission Unit) size mismatches that silently drop large handshake packets.
Typical symptoms
- docker pull fails with 'TLS handshake timeout' when downloading images
- docker push times out when uploading images to a registry
- docker build fails at the FROM instruction with TLS errors
- docker login hangs and eventually times out
- Error occurs consistently behind corporate proxies or VPNs
- Commands work from a home network but fail from the office network
- Intermittent failures that sometimes succeed on retry
- Error message mentions registry-1.docker.io or other registry URLs
Common causes
- MTU size mismatch between the Docker network and the host/VPN network, silently dropping large TLS handshake packets
- Corporate proxy not configured for the Docker daemon
- Proxy using https:// instead of http:// for the HTTPS_PROXY value
- VPN or firewall performing deep packet inspection or TLS interception
- Firewall throttling or partially blocking outbound TLS traffic on port 443
- DNS resolution delays slowing connection setup
- Network congestion or high latency to the registry
- System clock skew on the host, which can stall or break TLS negotiation
- IPv6 address selection issues where the host prefers an unroutable IPv6 path to the registry
How to fix "net/http: TLS handshake timeout"
First, determine if the issue is Docker-specific or a general network problem:
# Test TLS connection to Docker Hub
curl -v https://registry-1.docker.io/v2/
# Test DNS resolution
nslookup registry-1.docker.io
# Check if you can reach the auth server
curl -v https://auth.docker.io/If curl succeeds but Docker fails, the issue is likely with Docker's network or daemon configuration. If both fail, you have a network-level problem (MTU, proxy, firewall, or VPN).
MTU mismatches are the single most common cause of TLS handshake timeouts, especially on VPN, cloud, or virtualized networks. When the daemon sends handshake packets larger than the path can carry, they are silently dropped and the handshake stalls. Lowering the MTU is the fix that resolves this for most people, so try it early:
# Edit Docker daemon configuration
sudo nano /etc/docker/daemon.jsonAdd or update the MTU setting:
{
"mtu": 1400
}If that does not work, try an even lower value like 1300:
{
"mtu": 1300
}Restart Docker to apply:
sudo systemctl restart dockerNote: The default MTU is 1500, but VPNs and some cloud networks use smaller values due to encapsulation overhead. You can confirm your real path MTU with ip link show on the host interface, or by testing with ping -M do -s 1400 registry-1.docker.io.
If you are behind a corporate proxy, configure Docker with the correct proxy settings:
# Create the systemd drop-in directory
sudo mkdir -p /etc/systemd/system/docker.service.d
# Create the proxy configuration file
sudo nano /etc/systemd/system/docker.service.d/proxy.confAdd your proxy settings. Important: Use http:// (not https://) for the HTTPS_PROXY value if your proxy does not terminate TLS itself:
[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"Reload and restart Docker:
sudo systemctl daemon-reload
sudo systemctl restart dockerVerify the settings:
sudo systemctl show --property=Environment dockerTLS certificate validation depends on an accurate system clock. If the host clock is significantly wrong, certificate checks can stall or fail during the handshake and surface as a timeout. Verify and correct the time:
# Check current time and sync status
timedatectl status
# Enable NTP sync if it is off
sudo timedatectl set-ntp true
# Force an immediate resync (systemd-timesyncd)
sudo systemctl restart systemd-timesyncdThis is especially common on freshly provisioned VMs, suspended laptops, and containers whose clock has drifted.
DNS issues can delay connection setup and contribute to timeouts. Configure Docker to use reliable DNS servers:
# Edit Docker daemon configuration
sudo nano /etc/docker/daemon.jsonAdd DNS settings:
{
"dns": ["8.8.8.8", "8.8.4.4"]
}Restart Docker:
sudo systemctl restart dockerFor Docker Desktop (Windows/Mac): Go to Settings > Docker Engine and add the DNS configuration to the JSON editor.
VPNs and firewalls with deep packet inspection or TLS interception can interfere with handshakes:
1. Temporarily disconnect your VPN and retry the Docker command.
2. Temporarily disable your firewall only as a diagnostic, then re-enable it:
# On Ubuntu/Debian
sudo ufw disable
docker pull nginx:latest
sudo ufw enable
# On CentOS/RHEL
sudo systemctl stop firewalld
docker pull nginx:latest
sudo systemctl start firewalldIf Docker works without the VPN/firewall, do not leave them off. Instead configure proper exceptions:
- Allow outbound TLS (port 443) to registry-1.docker.io and auth.docker.io
- Add Docker Hub domains to your VPN split-tunnel configuration
- Disable TLS inspection specifically for registry traffic, or install the inspection proxy's CA certificate so Docker trusts the re-signed certs
Sometimes the host prefers an unroutable or slow IPv6 path to the registry while IPv4 works fine, which stalls the handshake. Prefer targeted fixes over disabling IPv6 system-wide.
First, confirm the IPv6 path is the problem:
# Compare IPv4 vs IPv6 reachability
curl -4 -v https://registry-1.docker.io/v2/
curl -6 -v https://registry-1.docker.io/v2/If only IPv6 hangs, bias address selection toward IPv4 via gai.conf instead of turning IPv6 off:
# Add to /etc/gai.conf to prefer IPv4 for outbound connections
echo 'precedence ::ffff:0:0/96 100' | sudo tee -a /etc/gai.confThis affects host-level getaddrinfo ordering without removing IPv6.
Last resort and strong caveat: Disabling IPv6 host-wide (via net.ipv6.conf.all.disable_ipv6=1 in sysctl) is broad and can break other services that rely on IPv6. Only do this if you fully control the host and have ruled out the targeted fixes above.
Sometimes Docker holds a stale connection or auth token. A restart plus a fresh login clears transient state:
# Restart Docker daemon
sudo systemctl restart docker
# Clear and refresh registry credentials
docker logout
docker login
# Retry the pull
docker pull nginx:latestIf the error is intermittent, use a retry loop:
for i in {1..5}; do
docker pull nginx:latest && break
echo "Attempt $i failed. Retrying in 10 seconds..."
sleep 10
doneFor CI/CD pipelines, add retry logic with backoff to absorb transient handshake failures.
Occasionally a specific Docker release introduces a regression that affects registry connectivity. Rather than assuming a particular version is broken, check what you are running and compare it against the official release notes and issue tracker for your version:
docker version- Review the [Docker Engine release notes](https://docs.docker.com/engine/release-notes/) for fixes related to networking or TLS for your version.
- Search the moby/moby GitHub issues for your exact version and symptom.
- If a documented regression matches, upgrade to a release where it is fixed (preferred), or pin to the last known-good version for your distro, e.g.:
# Example: install a specific version on Ubuntu/Debian
apt-cache madison docker-ce
sudo apt-get install docker-ce=<version-string>Upgrading forward is generally safer than downgrading.
If the registry is consistently slow or unreliable from your location, a mirror can help. Pick a mirror you can rely on:
# Edit daemon.json
sudo nano /etc/docker/daemon.json{
"registry-mirrors": ["https://your-mirror.example.com"]
}Restart Docker:
sudo systemctl restart dockerChoosing a mirror:
- Prefer a mirror your cloud provider offers in your region, or an internal pull-through cache (such as a self-hosted registry in mirror mode).
- Note that Google's public mirror.gcr.io has been deprecated/limited and is no longer a dependable general-purpose Docker Hub mirror, so do not rely on it as your primary fix.
- For specific images, alternative registries like ghcr.io or quay.io may be faster or more reliable.
Advanced notes
### Understanding the TLS Handshake
The TLS handshake involves multiple round trips between client and server:
1. Client Hello (client sends supported cipher suites)
2. Server Hello (server responds with chosen cipher)
3. Certificate exchange
4. Key exchange
5. Finished (encrypted channel established)
Any delay or packet loss during these steps can cause a timeout. The error does not mean the server is unreachable - it means the cryptographic negotiation could not complete in time. Because the handshake exchanges relatively large certificate packets, it is unusually sensitive to MTU/fragmentation problems, which is why lowering the MTU resolves so many cases.
### Docker Desktop Specific Issues
Windows:
- Check Windows Firewall rules for Docker
- Ensure the Hyper-V/WSL network adapter has correct DNS settings
- Try resetting the WSL 2 backend: wsl --shutdown then restart Docker Desktop
macOS:
- Reset Docker Desktop via Preferences > Troubleshoot > Reset to factory defaults
- Check whether the macOS firewall is blocking Docker
### Debugging with Verbose Output
Enable debug logging to see more details:
# Edit daemon.json
sudo nano /etc/docker/daemon.json{
"debug": true
}View logs:
sudo journalctl -u docker -f### Proxy Protocol Considerations
A common mistake is using https:// for the HTTPS_PROXY value. Most corporate proxies expect a plain HTTP connection from the client even when proxying HTTPS traffic:
# Wrong (often causes TLS handshake issues):
Environment="HTTPS_PROXY=https://proxy.example.com:8080"
# Correct:
Environment="HTTPS_PROXY=http://proxy.example.com:8080"The proxy handles the TLS connection to the destination server, so your connection to the proxy itself does not need to be HTTPS.
### CI/CD Pipeline Considerations
In CI/CD environments:
- Add retry logic with exponential backoff
- Cache base images in your own registry or pull-through cache
- Use authenticated pulls to avoid rate limiting
- Consider a registry mirror provided by your CI platform
### Verifying Typos
Double-check your image name - a typo can also produce confusing errors because Docker tries to connect to a non-existent repository.
Frequently asked questions
- What does "net/http: TLS handshake timeout" mean?
- io). The TLS handshake is the process where your Docker client and the registry server exchange cryptographic keys and certificates to establish a secure, encrypted connection. When this handshake takes longer than the timeout period (typically 10-15 seconds), Docker aborts the connection and displays this error.
- How do I fix "net/http: TLS handshake timeout"?
- The 'net/http: TLS handshake timeout' error occurs when Docker cannot complete a TLS handshake with a registry within the allowed time. It is usually caused by MTU mismatches, proxy misconfiguration, firewall/VPN packet inspection, or network latency rather than the registry being down.
- What causes "net/http: TLS handshake timeout"?
- MTU size mismatch between the Docker network and the host/VPN network, silently dropping large TLS handshake packets Corporate proxy not configured for the Docker daemon Proxy using https:// instead of http:// for the HTTPS_PROXY value
- Is the "net/http: TLS handshake timeout" error serious?
- It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.
Official resources & further reading
- 📚Docker Official Documentation
- 💬
- 💬Docker pull: TLS handshake timeout - Server Fault(Stack Overflow)
- 💬
- 💬Net/http: TLS handshake timeout - Docker Community Forums(Stack Overflow)
- 🐙
- 💬
- 📖Configure the daemon with systemd - Docker Docs(Official Docs)
- 💬100 Common Docker Errors & Solutions - DEV Community(Stack Overflow)