Docker returns HTTP 407 "Proxy Authentication Required" when pulling images through a corporate proxy that needs credentials. Fix it by configuring authenticated proxy settings for the Docker daemon.
The "Proxy Authentication Required" error (HTTP 407) means the Docker daemon is behind a proxy server that requires authentication, and valid proxy credentials are either missing, incorrect, or not configured where the daemon can read them. When you run `docker pull`, `docker login`, or `docker build`, the daemon connects to a container registry such as registry-1.docker.io. If outbound traffic is routed through an authenticated proxy, the proxy returns 407 and refuses to forward the request until it receives valid credentials. This is common in corporate environments where all outbound traffic must traverse a proxy, often backed by Active Directory/LDAP. The key detail that trips people up is that the Docker daemon does not read your shell's `HTTP_PROXY`/`HTTPS_PROXY` environment variables — it has its own proxy configuration (a systemd drop-in, or the `proxies` block in `daemon.json` on Docker 23.0+) that must be set separately and then loaded with a daemon restart.
The Docker daemon does not read your shell environment. On systemd-based Linux, give it a drop-in override:
# Create the override directory
sudo mkdir -p /etc/systemd/system/docker.service.d
# Create the proxy configuration file
sudo tee /etc/systemd/system/docker.service.d/http-proxy.conf > /dev/null << 'EOF'
[Service]
Environment="HTTP_PROXY=http://username:[email protected]:8080"
Environment="HTTPS_PROXY=http://username:[email protected]:8080"
Environment="NO_PROXY=localhost,127.0.0.1,.example.com"
EOFReplace username, password, proxy.example.com, and 8080 with your real values.
Secure the file — it contains credentials in plaintext:
sudo chmod 600 /etc/systemd/system/docker.service.d/http-proxy.conf
sudo chown root:root /etc/systemd/system/docker.service.d/http-proxy.confReload systemd and restart Docker:
sudo systemctl daemon-reload
sudo systemctl restart dockerVerify the variables were applied:
sudo systemctl show --property=Environment dockerIf possible, prefer a dedicated proxy service account over your personal credentials, since the secret is stored on disk.
Docker Engine 23.0 and later support a proxies block in the daemon configuration file, which is often cleaner than a systemd drop-in:
sudo tee /etc/docker/daemon.json > /dev/null << 'EOF'
{
"proxies": {
"http-proxy": "http://username:[email protected]:8080",
"https-proxy": "http://username:[email protected]:8080",
"no-proxy": "localhost,127.0.0.1,.example.com"
}
}
EOFRestrict permissions, since the password is stored in cleartext:
sudo chmod 600 /etc/docker/daemon.json
sudo chown root:root /etc/docker/daemon.jsonRestart Docker to apply:
sudo systemctl restart dockerAvoid defining the proxy in both `daemon.json` and a systemd drop-in at the same time — pick one source of truth to prevent confusing precedence conflicts. If you switch to daemon.json, remove the systemd http-proxy.conf file.
If the proxy password contains characters that are significant inside a URL, encode them or the daemon will parse the proxy URL incorrectly. Common encodings:
| Character | URL Encoded |
|-----------|-------------|
| @ | %40 |
| # | %23 |
| $ | %24 |
| % | %25 |
| & | %26 |
| ! | %21 |
| ? | %3F |
| = | %3D |
| : | %3A |
| space | %20 |
Example: the password P@ss#word! becomes P%40ss%23word%21:
Environment="HTTPS_PROXY=http://username:P%40ss%23word%[email protected]:8080"Note that you must URL-encode the value even when using EnvironmentFile= or daemon.json, because the proxy URL is parsed as a URL in every case. If you use an environment file, secure it the same way:
sudo tee /etc/docker/proxy.env > /dev/null << 'EOF'
HTTP_PROXY=http://username:P%40ss%23word%[email protected]:8080
HTTPS_PROXY=http://username:P%40ss%23word%[email protected]:8080
NO_PROXY=localhost,127.0.0.1,.example.com
EOF
sudo chmod 600 /etc/docker/proxy.env
sudo chown root:root /etc/docker/proxy.env
sudo tee /etc/systemd/system/docker.service.d/http-proxy.conf > /dev/null << 'EOF'
[Service]
EnvironmentFile=/etc/docker/proxy.env
EOF
sudo systemctl daemon-reload
sudo systemctl restart dockerDocker's Go-based runtime chooses the proxy variable based on the request's URL scheme. Registry traffic to https://registry-1.docker.io/v2/ is selected by HTTPS_PROXY, so if only HTTP_PROXY is set, HTTPS registry requests are sent directly (or unproxied) and fail. Always set both:
Environment="HTTP_PROXY=http://username:[email protected]:8080"
Environment="HTTPS_PROXY=http://username:[email protected]:8080"The proxy URL itself normally uses the http:// scheme even for HTTPS_PROXY: the client opens an HTTP CONNECT tunnel to the proxy and the end-to-end TLS to the registry is then tunneled through it. Use an https:// proxy scheme only if your proxy explicitly terminates TLS on its listener.
Confirm both variables are present on the daemon:
sudo systemctl show --property=Environment docker | tr ' ' '\n' | grep -i proxyOn Docker Desktop the daemon runs inside a managed VM, so configure the proxy through the app rather than editing host systemd files:
1. Open Docker Desktop and click the gear icon (Settings).
2. Go to Resources > Proxies.
3. Enable Manual proxy configuration.
4. Enter the proxy URLs (including credentials if required):
- Web Server (HTTP): http://username:[email protected]:8080
- Secure Web Server (HTTPS): http://username:[email protected]:8080
- Bypass: localhost,127.0.0.1
5. Click Apply & Restart.
Note: the proxies block in ~/.docker/config.json configures proxies that are injected into containers at build/run time, not the daemon's own registry connection. For the daemon's proxy on Docker Desktop, use the Settings UI above.
If the corporate proxy requires NTLM, NTLMv2, or Kerberos/Negotiate authentication (common with Microsoft proxies), Docker cannot authenticate directly. Run a local proxy bridge such as Cntlm that handles the NTLM handshake and exposes an unauthenticated local endpoint:
# Install Cntlm
sudo apt-get install cntlm # Debian/Ubuntu
sudo dnf install cntlm # RHEL/FedoraGenerate a password hash so the cleartext password does not have to live in the config:
cntlm -H -u username -d DOMAINPut the generated PassNTLMv2 hash into /etc/cntlm.conf (do not store the plaintext Password line in production):
Username your_username
Domain YOUR_DOMAIN
PassNTLMv2 <hash-from-cntlm-H>
Proxy corporate-proxy.example.com:8080
NoProxy localhost, 127.0.0.*, 10.*, .example.com
Listen 127.0.0.1:3128Secure and start it:
sudo chmod 600 /etc/cntlm.conf
sudo systemctl enable --now cntlmThen point the Docker daemon at the local, credential-free bridge (no secrets in the Docker config):
Environment="HTTP_PROXY=http://127.0.0.1:3128"
Environment="HTTPS_PROXY=http://127.0.0.1:3128"Confirm the daemon picked up the proxy and can reach a registry:
# Docker reports its configured proxies near the bottom of the output
docker info
# Pull a small image end to end
docker pull hello-worldIf it still fails, raise the daemon log level via daemon.json (do not run a second dockerd by hand while the service is running):
{
"debug": true
}Then restart and follow the logs:
sudo systemctl restart docker
sudo journalctl -u docker.service -fYou can also reproduce the request outside Docker to isolate the proxy from the daemon:
https_proxy='http://username:[email protected]:8080' \
curl -sv https://registry-1.docker.io/v2/A 200 or 401 Unauthorized (registry auth, not 407) from this curl confirms the proxy itself is accepting your credentials.
If docker login itself reports proxy/auth problems after the proxy is correctly configured, refresh the stored registry credentials. Prefer docker logout over deleting your config file, because ~/.docker/config.json also holds your proxy and credential-store settings:
# Remove cached Docker Hub auth token only
docker logout
# Log back in once the proxy is working
docker loginIf you suspect a corrupt credential helper, inspect (do not blindly delete) the config:
grep -E 'credsStore|credHelpers' ~/.docker/config.jsonOnly if the file is genuinely corrupt, back it up before removing it so you can restore your proxy block:
cp ~/.docker/config.json ~/.docker/config.json.bakProxy configuration precedence
If a proxy is defined in more than one place, the effective value can be surprising. Keep a single source of truth. In practice: daemon.json proxies and the systemd drop-in both configure the daemon — do not set both. The daemon also does not read your interactive shell's exported variables, which is the most common reason "it works in my terminal but Docker still 407s."
Daemon proxy vs. container proxy
These are two independent settings:
- Daemon proxy (this article): used by the daemon to pull/push images. Set via systemd drop-in or daemon.json proxies.
- Container proxy: injected into containers for apt-get, npm, etc. Set via the proxies block in ~/.docker/config.json or per-run with docker run -e HTTPS_PROXY=....
SSL/TLS-inspecting proxies
Many corporate proxies perform TLS interception (they re-sign traffic with an internal CA). This causes x509 certificate errors *in addition to* 407 auth errors. The correct, secure fix is to install the corporate root CA so the daemon trusts the re-signed certificates:
sudo cp corporate-ca.crt /usr/local/share/ca-certificates/corporate-ca.crt
sudo update-ca-certificates
sudo systemctl restart dockerOn RHEL/Fedora, place the CA in /etc/pki/ca-trust/source/anchors/ and run sudo update-ca-trust.
Do not add "insecure-registries": ["registry-1.docker.io"] to work around this. That flag disables TLS verification for the registry, leaves the daemon open to man-in-the-middle attacks on your image pulls, and does not actually solve an SSL-inspection certificate problem — installing the corporate CA is the only correct fix. Reserve insecure-registries for a private registry you fully control on a trusted network, never for Docker Hub.
PAC files
Docker does not evaluate Proxy Auto-Config (PAC) files. If your organization uses one, read the PAC to determine the concrete proxy host:port for registry hosts and configure Docker with that direct value.
Air-gapped or proxy-hostile environments
If proxy configuration is impractical, run an internal registry mirror or pull-through cache and point Docker at it via a registry-mirrors entry in daemon.json, so day-to-day pulls never traverse the corporate proxy at all.
Container exited with code 128: invalid exit argument
How to fix 'Container exited with code 128' in Docker
error exporting cache: failed to export cache
How to fix 'error exporting cache: failed to export cache' in Docker
net/http: TLS handshake timeout
How to fix 'net/http: TLS handshake timeout' in Docker
Docker container exited with code 255
How to fix 'Container exited with code 255' in Docker
Read-only file system
How to fix 'Read-only file system' in Docker