Overview
If docker pull fails behind a corporate proxy, it’s almost always the Docker daemon (dockerd) lacking correct proxy settings or trust for your proxy’s TLS certificate. The client binary (docker) talks to dockerd over a local socket; the daemon performs the registry requests. Configure proxy variables for the daemon, set NO_PROXY correctly, and install your proxy’s CA if it intercepts TLS.
Common errors and likely causes:
| Error snippet | Likely cause |
|---|---|
| x509: certificate signed by unknown authority | Missing corporate proxy/registry CA |
| proxyconnect tcp: dial tcp ...: i/o timeout | Wrong proxy URL or unreachable proxy |
| unexpected status code 407/403 | Auth required/wrong credentials for proxy |
| no such host / TLS handshake timeout | NO_PROXY or DNS misconfig |
Quickstart (Linux with systemd)
Goal: make dockerd use the proxy, skip it for internal hosts, and trust the corporate CA.
- Create a systemd drop-in for Docker’s proxy.
- Set HTTP_PROXY, HTTPS_PROXY, and a precise NO_PROXY.
- Reload, restart, verify.
Minimal commands:
# 1) Create drop-in directory
sudo mkdir -p /etc/systemd/system/docker.service.d
# 2) Configure proxy env for the Docker daemon
sudo tee /etc/systemd/system/docker.service.d/http-proxy.conf >/dev/null <<'EOF'
[Service]
Environment="HTTP_PROXY=http://proxy.corp.example.com:3128"
Environment="HTTPS_PROXY=http://proxy.corp.example.com:3128"
Environment="NO_PROXY=localhost,127.0.0.1,::1,registry-1.docker.io,auth.docker.io,.docker.io,.docker.com,registry.internal.example.com,10.0.0.0/8,192.168.0.0/16"
EOF
# 3) Trust the corporate proxy/registry CA (Debian/Ubuntu)
sudo install -m 0644 corp-proxy-ca.crt /usr/local/share/ca-certificates/corp-proxy-ca.crt
sudo update-ca-certificates
# RHEL/CentOS/AlmaLinux: place cert in /etc/pki/ca-trust/source/anchors/ then run:
# sudo update-ca-trust
# 4) Apply and restart Docker
sudo systemctl daemon-reload
sudo systemctl restart docker
# 5) Verify the daemon env and pull
systemctl show docker -p Environment
sudo docker pull busybox:latest
If your proxy requires auth, encode credentials in the URL safely, for example:
export HTTPS_PROXY="http://user:[email protected]:3128"
Percent-encode special characters in user/password.
Minimal Working Example (MWE)
A self-contained working configuration for a host behind proxy.corp.example.com:3128 with an internal registry registry.internal.example.com, skipping the proxy for Docker Hub and internal networks.
# /etc/systemd/system/docker.service.d/http-proxy.conf
[Service]
Environment="HTTP_PROXY=http://proxy.corp.example.com:3128"
Environment="HTTPS_PROXY=http://proxy.corp.example.com:3128"
Environment="NO_PROXY=localhost,127.0.0.1,::1,registry-1.docker.io,auth.docker.io,.docker.io,.docker.com,registry.internal.example.com,10.0.0.0/8,192.168.0.0/16"
Apply with:
sudo systemctl daemon-reload && sudo systemctl restart docker
sudo docker pull alpine:3.20
Notes:
- Use uppercase variable names for systemd.
- NO_PROXY accepts comma-separated hosts, literal IPs, and domain suffixes (prefix with a dot). Some stacks do not honor CIDR ranges; prefer explicit hosts/IPs if in doubt.
Step-by-step diagnosis and fix
- Confirm the daemon sees proxy variables
- Check drop-ins: ls -l /etc/systemd/system/docker.service.d/
- Print env: systemctl show docker -p Environment
- If empty/missing, your drop-in file path or syntax is wrong.
- Verify proxy reachability and credentials
- From the host: curl -x http://proxy.corp.example.com:3128 https://registry-1.docker.io/v2/ -I
- Expect 200/401/403. 407 means proxy auth required; configure credentials.
- Tune NO_PROXY
- Always include localhost, 127.0.0.1, ::1.
- Add internal registries (e.g., registry.internal.example.com) and any endpoints that your proxy breaks.
- To bypass for all Docker Hub subdomains, include .docker.io and .docker.com.
- Install trust for corporate CA
- System trust: place corp-proxy-ca.crt into the OS trust store and run update-ca-certificates (Debian/Ubuntu) or update-ca-trust (RHEL family).
- Private registry trust: for a private hostname, you can also place the CA at /etc/docker/certs.d/registry.internal.example.com/ca.crt to scope trust to that registry.
- Restart Docker and retest
- sudo systemctl restart docker
- sudo docker pull busybox:latest
- Keep the client environment clean
- Unset client-level proxy for Docker commands to avoid confusion: unset HTTP_PROXY HTTPS_PROXY NO_PROXY
- The daemon performs the pull; client proxies only affect commands that directly call the network (e.g., docker login).
Pitfalls and gotchas
- Wrong place: Editing /etc/docker/daemon.json proxies affects container runtime egress, not the daemon’s own pulls. Use systemd env for dockerd.
- Typos and casing: HTTP_PROXY/HTTPS_PROXY/NO_PROXY must be uppercase for systemd. Commas only; no spaces.
- Missing daemon reload: Always run systemctl daemon-reload after changing drop-ins.
- Proxy auth characters: If your password has @ or :, percent-encode them in the proxy URL.
- TLS interception without trust: MITM proxies require installing their CA, or pulls will fail with x509 errors.
- Rootless Docker: Configure the user-level systemd service (systemctl --user edit docker) or set env before starting dockerd-rootless; path differs from this guide.
- Docker Desktop: Use the UI to set proxies; this article targets Linux servers.
Performance notes
- Use a local caching registry or proxy (Artifactory, Nexus, Harbor) to accelerate repeated pulls. Add it as a mirror:
{
"registry-mirrors": ["https://registry.internal.example.com"]
}
Place this in /etc/docker/daemon.json and restart Docker.
- Keep NO_PROXY minimal. Overly broad NO_PROXY routes too much traffic direct, hurting reliability.
- Prefer HTTPS proxies. Even when specified as http:// in the URL, HTTPS_PROXY tunnels TLS via CONNECT to preserve end-to-end encryption when not intercepted.
FAQ
Does the Docker daemon actually use HTTP_PROXY/HTTPS_PROXY? Yes. Dockerd inherits environment variables from systemd. That’s why the systemd drop-in is required.
Should I set proxy variables in /etc/profile or the shell? You can for general tools, but Docker pulls depend on dockerd’s environment, not your shell. Configure systemd.
How do I set proxies for containers’ outbound traffic? Use the proxies section in /etc/docker/daemon.json or set env vars per container. This is separate from daemon proxy.
What about containerd-only hosts? Configure proxy env on the containerd service (e.g., /etc/systemd/system/containerd.service.d/http-proxy.conf) similarly, then restart containerd.
How do I bypass the proxy for a single pull? Temporarily run with a clean environment so the client doesn’t interfere and rely on daemon config: env -u HTTP_PROXY -u HTTPS_PROXY -u NO_PROXY sudo docker pull alpine