The 'net/http: TLS handshake timeout' error occurs when Docker cannot complete a TLS (Transport Layer Security) handshake with a registry within the allowed time. This is typically caused by network latency, proxy misconfigurations, firewall restrictions, or MTU issues.
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 suggests that packets are either being delayed, dropped, or blocked somewhere in the network path between Docker and the registry. This error is common in corporate networks with deep packet inspection, on VPN connections, or when there are MTU (Maximum Transmission Unit) size mismatches.
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 configuration. If both fail, you have a network-level problem.
If you're 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 doesn't support HTTPS connections:
[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,registry-1.docker.io"Reload and restart Docker:
sudo systemctl daemon-reload
sudo systemctl restart dockerVerify the settings:
sudo systemctl show --property=Environment dockerMTU mismatches are a common cause of TLS handshake timeouts, especially on VPN or virtualized networks. Lower the MTU to prevent packet fragmentation:
# Edit Docker daemon configuration
sudo nano /etc/docker/daemon.jsonAdd or update the MTU setting:
{
"mtu": 1400
}If that doesn't 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.
DNS issues can delay connection setup and contribute to timeouts. Configure Docker to use reliable public 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.
Docker allows you to increase the default TLS handshake timeout. Set the DOCKER_CLIENT_TIMEOUT environment variable:
# Set a longer timeout (in seconds)
export DOCKER_CLIENT_TIMEOUT=120
# Then run your Docker command
docker pull nginx:latestTo make this permanent, add it to your shell profile:
echo 'export DOCKER_CLIENT_TIMEOUT=120' >> ~/.bashrc
source ~/.bashrcFor Docker daemon-level timeout, you can adjust the daemon configuration with the --tls-handshake-timeout flag if available in your Docker version.
VPNs and firewalls with deep packet inspection can interfere with TLS handshakes:
1. Temporarily disconnect your VPN and try the Docker command again
2. Temporarily disable your firewall to test:
# 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 VPN/firewall, configure exceptions:
- Allow outbound connections on port 443 to registry-1.docker.io
- Add Docker Hub domains to your VPN's split tunnel configuration
- Disable TLS inspection for Docker registry traffic
IPv6 can sometimes cause connectivity issues with Docker registries:
# Add to /etc/sysctl.conf
sudo nano /etc/sysctl.confAdd these lines:
net.ipv6.conf.all.disable_ipv6=1
net.ipv6.conf.default.disable_ipv6=1
net.ipv6.conf.lo.disable_ipv6=1Apply the changes:
sudo sysctl --systemRestart Docker:
sudo systemctl restart dockerFor macOS: Disable IPv6 in System Preferences > Network > Advanced > TCP/IP.
Docker versions 28.0.2 through 28.1.1 have been reported to cause TLS handshake timeout issues. Check your version:
docker versionIf you're running an affected version, consider downgrading to 28.0.1:
# On Ubuntu/Debian - install specific version
sudo apt-get install docker-ce=5:28.0.1-1~ubuntu.22.04~jammy
# Or upgrade to the latest version if a fix has been released
sudo apt-get update && sudo apt-get upgrade docker-ceCheck the Docker release notes for fixes related to TLS handshake issues.
Sometimes Docker gets stuck in an inconsistent state. A simple restart can resolve transient issues:
# Restart Docker daemon
sudo systemctl restart docker
# Retry the pull command
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 to handle transient failures.
If Docker Hub is consistently slow or unreliable from your location, configure a registry mirror:
# Edit daemon.json
sudo nano /etc/docker/daemon.jsonAdd a mirror:
{
"registry-mirrors": ["https://mirror.gcr.io"]
}Restart Docker:
sudo systemctl restart dockerOther mirror options:
- Your cloud provider may offer a regional mirror
- Corporate environments often have internal registry mirrors
- Consider using alternative registries like ghcr.io or quay.io for specific images
### 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 doesn't mean the server is unreachable - it means the cryptographic negotiation couldn't complete in time.
### Docker Desktop Specific Issues
Windows:
- Check Windows Firewall rules for Docker
- Ensure Hyper-V 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 if 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 HTTP connections 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 doesn't 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
- Use authenticated pulls to avoid rate limiting
- Consider using registry mirrors provided by your CI platform
### Verifying Typos
Double-check your image name - a typo can also produce TLS timeout errors because Docker tries to connect to a non-existent repository.
image operating system "linux" cannot be used on this platform
How to fix 'image operating system linux cannot be used on this platform' in Docker
manifest unknown: manifest unknown
How to fix 'manifest unknown' in Docker
cannot open '/etc/passwd': Permission denied
How to fix 'cannot open: Permission denied' in Docker
Error response from daemon: failed to create the ipvlan port
How to fix 'failed to create the ipvlan port' in Docker
toomanyrequests: Rate exceeded for anonymous users
How to fix 'Rate exceeded for anonymous users' in Docker Hub