1. Home
  2. Blog
  3. Installing Clash on Windows: Full Walkthrough
9 min read

Clash Docker Hub Timeouts: Advanced Transparent Proxy Setup

Windows Clash setup: download, install, subscription import, system proxy, auto-start, plus fixes for antivirus false positives and port conflicts.

Why Docker Hub Times Out While the Browser Works

A successful browser test does not prove that Docker can reach Docker Hub. The browser usually follows the operating system's system-proxy setting or explicit HTTP_PROXY/HTTPS_PROXY variables. Docker image pulls follow a different path: the Docker CLI sends a request to the Docker daemon, and the daemon performs the registry access on its own. On a Linux host, that daemon commonly runs as dockerd under systemd; on Docker Desktop, it runs inside a managed Linux virtual machine. The process that needs proxy access is therefore not necessarily the process that opened the browser.

An image pull can also involve several domains rather than one continuous connection to docker.io. The client may first contact Docker Hub for authentication, obtain a token from auth.docker.io, query registry-1.docker.io for image manifests, and then download layers from a content delivery endpoint. If DNS resolves one of these names incorrectly, if only one domain is routed through Clash, or if the daemon cannot reach the local proxy listener, the final error may simply appear as context deadline exceeded, i/o timeout, or TLS handshake timeout.

The first diagnostic principle is to identify the network namespace and process that owns each connection. A browser running on the host, a container process, the Docker CLI, and the Docker daemon may all use different proxy settings. A transparent proxy must intercept the correct traffic path; adding a proxy environment variable to a container will not automatically change how the daemon downloads an image, and enabling Clash's system proxy switch will not automatically configure dockerd.

Notice

Do not start by changing every Clash rule to Global mode. First determine whether the timeout belongs to the host daemon, a container, Docker Desktop's virtual machine, or DNS. Otherwise a temporary workaround can hide the actual routing error.

Trace the Full Request Path Before Changing the Configuration

Begin with the Docker client and daemon separately. Run a normal pull and note the exact stage where it fails:

docker pull hello-world
docker info
journalctl -u docker --since "15 minutes ago" --no-pager

docker info confirms whether the CLI can reach the daemon, but it does not test Docker Hub itself. The daemon log is more useful because it can reveal whether the failure occurred during authentication, manifest retrieval, layer download, DNS lookup, or TLS negotiation. A message mentioning lookup points toward DNS; connect: connection refused usually means a listener or port problem; repeated Client.Timeout exceeded while awaiting headers suggests that the request reached a network path but did not receive a response in time.

Next, test the relevant domains from the host without assuming that the browser's proxy is being used:

getent hosts auth.docker.io
getent hosts registry-1.docker.io
curl -I --connect-timeout 10 https://registry-1.docker.io/v2/
curl -I --connect-timeout 10 https://auth.docker.io/token

A 401 Unauthorized response from registry-1.docker.io/v2/ is generally a useful result: it proves that DNS, TCP, TLS, and HTTP reached the registry, and that authentication is now the expected next step. A timeout is a transport problem, while an HTTP 401 is not evidence that the registry is unavailable.

To determine whether the daemon inherits any proxy environment, inspect its service definition rather than your interactive shell:

systemctl show --property=Environment docker
systemctl cat docker
sudo tr '\0' '\n' < /proc/$(pgrep -xo dockerd)/environ | grep -i proxy

For Docker Desktop, inspect the Docker Desktop proxy settings and test from the Desktop-managed environment where possible. A host-side curl test may succeed because the host is using Clash, while the Linux VM used by Docker Desktop still has a separate route and DNS configuration.

Choose the Correct Proxy Layer

There are three practical designs, and they solve different problems. The simplest is to configure an explicit HTTP or HTTPS proxy for the Docker daemon. This is usually the best first fix when the goal is only to pull images. The daemon opens a connection to the local Clash mixed or HTTP proxy port, and Clash then applies its normal domain rules. The container network does not need to be transparent for image pulls.

The second design is transparent proxying with Clash Meta or mihomo TUN mode. TUN creates a virtual interface and captures IP traffic from the host routing stack. This is useful when several applications, services, or containers must be handled without individually configuring proxy variables. It also avoids relying on whether a client understands HTTP proxy settings. However, TUN requires elevated privileges, correct route exclusions, and careful DNS handling.

The third design is a container-network-specific redirect using policy routing and nftables or iptables. This can capture traffic from Docker bridge interfaces and send it to a transparent proxy listener such as a redirection or TProxy port. It offers precise control, but it is more sensitive to kernel modules, marks, return routes, IPv6, and Docker's own firewall chains. It should be used when TUN cannot cover the required namespace or when the operator needs per-network interception.

Design Best use Main limitation
Docker daemon HTTP proxy Image pulls with minimal system changes Only software that uses the configured daemon proxy is covered
mihomo TUN Host and broad application traffic Requires route and DNS design, especially with Docker subnets
nftables or TProxy redirect Selective interception of bridge or service traffic More complex rules, marks, and return-path requirements

Set an Explicit Docker Daemon Proxy as the Baseline

Before building a transparent design, test an explicit daemon proxy. Assume Clash is listening on the host at 127.0.0.1:7890 and that this port accepts HTTP proxy requests. Create a systemd drop-in directory and add the proxy variables:

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://127.0.0.1:7890"
Environment="HTTPS_PROXY=http://127.0.0.1:7890"
Environment="NO_PROXY=localhost,127.0.0.1,::1,registry.local,.local"
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
systemctl show --property=Environment docker

The exact port and protocol must match the Clash listener. If the listener is SOCKS5-only, an HTTP proxy URL will fail even though the port is open. Use the mixed port when available, or place a compatible local adapter in front of the SOCKS listener. Do not write https://127.0.0.1:7890 merely because the destination is HTTPS; the URL describes the protocol spoken to the local proxy. Many local proxy listeners accept plain HTTP CONNECT and then tunnel HTTPS destinations.

NO_PROXY is important. Keep local addresses, the Docker socket's local control paths, internal registries, and private service domains out of the external proxy path. Avoid putting docker.io in NO_PROXY, because that would deliberately bypass Clash for the registry. After restarting, retry the pull and compare the daemon log. If the explicit proxy works, the node, Clash rules, and registry access are probably sound; the previous issue was that the daemon had no usable proxy configuration.

For Docker Desktop, use its proxy configuration rather than editing the Linux host's docker.service. Desktop's daemon is inside its managed environment, so 127.0.0.1 refers to that environment, not necessarily the host operating system. A host listener may need to bind to an address reachable from the Desktop VM, and firewall rules must allow that connection. Binding a proxy to all interfaces without access controls is unsafe; restrict the listener to trusted local networks whenever the platform allows it.

Build a Reliable mihomo TUN Design

When the requirement is broader than Docker image pulls, use mihomo TUN as the transparent layer. The exact field names can vary with the client and core version, but the design should contain four deliberate parts: enable the TUN interface, install routes, choose a DNS strategy, and exclude traffic that must never be captured. A conceptual configuration looks like this:

tun:
  enable: true
  stack: mixed
  auto-route: true
  auto-detect-interface: true
  strict-route: true

dns:
  enable: true
  enhanced-mode: fake-ip
  nameserver:
    - https://1.1.1.1/dns-query
    - https://dns.google/dns-query
  fake-ip-filter:
    - "*.lan"
    - "*.local"
    - "localhost"

rules:
  - DOMAIN-SUFFIX,docker.io,Proxy
  - DOMAIN-SUFFIX,docker.com,Proxy
  - DOMAIN,auth.docker.io,Proxy
  - DOMAIN,registry-1.docker.io,Proxy
  - MATCH,DIRECT

This is a design example, not a complete subscription profile. The proxy group must already exist, and the DNS upstreams must be reachable in the intended environment. The Docker Hub rules should appear before broad direct rules such as regional or private-network rules. A later DOMAIN-SUFFIX,io,DIRECT, for example, can override the intended policy if it is placed above the Docker rules. Rule matching is top to bottom and stops at the first match.

Docker's default bridge networks often use private ranges such as 172.17.0.0/16, while custom networks may use other RFC1918 ranges. TUN route installation must not create a loop in which Clash captures its own upstream connection or intercepts Docker's internal gateway traffic. Preserve routes to the host's physical gateway, the Clash process's upstream interface, and Docker bridge networks as appropriate for the chosen architecture. If the host reaches the proxy server through a specific interface, use route exclusions or policy-routing rules so that the proxy's own connection does not return to the TUN interface.

Notice

Do not copy a route-exclusion list from another machine without checking its interfaces and Docker subnets. A route that is correct on a laptop can break a server when the default gateway, bridge range, or IPv6 prefix is different.

Fake-IP DNS deserves special attention. With fake-IP mode, a domain may resolve to a synthetic address from a configured pool. Clash uses its internal mapping to recover the original domain and apply domain rules. If Docker or another component bypasses Clash DNS and receives a normal IP from the host resolver, the connection may be matched only by IP rules, making domain-based Docker rules appear ineffective. Conversely, if a private registry returns an address that must remain reachable directly, add it to the appropriate fake-IP filter or use a dedicated nameserver policy.

Handle Docker Bridge Networks and DNS Explicitly

Containers normally receive a resolver address from Docker, often the embedded resolver at 127.0.0.11 inside the container namespace. That address is not the same as the host's loopback address. A container configured with dns: 127.0.0.1 is usually pointing to itself, not to Clash on the host. If containers need Clash DNS, provide a host-reachable resolver address, or let Docker's embedded resolver forward to a correctly configured host resolver.

Inspect the actual network and resolver values instead of guessing:

docker network inspect bridge
docker run --rm busybox cat /etc/resolv.conf
docker run --rm busybox nslookup registry-1.docker.io
ip route
ip addr show docker0

For a Linux host, a container can often reach a service bound to the Docker bridge gateway, but the exact gateway address depends on the network. A Clash DNS listener bound only to 127.0.0.1 will not accept requests from a bridge namespace. If you bind DNS or a proxy listener to a bridge-reachable address, protect it with firewall rules and do not expose an open resolver or proxy to untrusted networks.

Keep internal and external DNS policies separate. Internal names such as registry.company.lan should use the corporate resolver and normally be routed DIRECT. Public Docker Hub names can use Clash's remote DNS policy and a proxy route. A split-DNS arrangement is often more stable than forcing every name through one encrypted upstream. Test both the answer and the route: a correct IP address does not prove that the subsequent TCP connection uses the intended interface.

  • Check the host resolver for auth.docker.io and registry-1.docker.io.
  • Check the resolver inside a representative container.
  • Confirm that private registry names resolve to private addresses and do not enter the public proxy path.
  • Confirm that IPv6 is either fully supported by the route and proxy design or deliberately disabled for the affected path; a broken IPv6 preference can look like an intermittent timeout.

Write Rules for the Registry Flow, Not Just the Brand Name

A rule for DOMAIN-SUFFIX,docker.com,Proxy alone may not cover the entire pull. Authentication and layer delivery can use different hostnames, and a registry mirror may redirect requests to its own domain. Start with the domains visible in daemon logs, DNS queries, or a packet trace. Typical Docker Hub access includes auth.docker.io, registry-1.docker.io, and docker.io; layer delivery can involve additional CDN hostnames that should be verified rather than invented.

Place specific rules before broad rules, and keep private registries separate:

rules:
  - DOMAIN,auth.docker.io,Proxy
  - DOMAIN,registry-1.docker.io,Proxy
  - DOMAIN-SUFFIX,docker.io,Proxy
  - DOMAIN-SUFFIX,docker.com,Proxy
  - DOMAIN-SUFFIX,company.lan,DIRECT
  - IP-CIDR,10.0.0.0/8,DIRECT,no-resolve
  - IP-CIDR,172.16.0.0/12,DIRECT,no-resolve
  - IP-CIDR,192.168.0.0/16,DIRECT,no-resolve
  - MATCH,DIRECT

The no-resolve option on private IP rules prevents Clash from performing an unnecessary domain resolution for an IP-only condition. Use it only where the rule syntax and core support it. Do not add broad private-range direct rules above domain rules if the destination is represented by a fake IP and the domain policy must win; understand how the selected enhanced DNS mode and rule engine interact on that client.

During diagnosis, temporarily route the Docker Hub domains through a known working proxy group rather than an automatic group with an unavailable node. Once pulls succeed, restore the normal group and test failover. This separates a policy-selection problem from a registry connectivity problem and avoids confusing a slow url-test measurement with a failed image download.

Diagnose Timeouts Layer by Layer

When the setup still fails, test one layer at a time. First test name resolution, then TCP reachability, then TLS, then HTTP authentication, and finally the complete Docker pull. For example:

getent hosts registry-1.docker.io
nc -vz registry-1.docker.io 443
curl -v --connect-timeout 10 https://registry-1.docker.io/v2/
docker pull hello-world
journalctl -u docker -f

If getent fails, changing proxy rules will not repair a missing resolver path. If DNS succeeds but nc cannot connect, inspect the route table, firewall, TUN exclusions, and upstream node. If TCP connects but curl stalls during TLS, check whether the selected proxy supports the destination, whether the system clock is correct, and whether IPv6 is taking a broken route. If curl receives 401 but docker pull fails, focus on daemon proxy inheritance, authentication flow, registry redirects, and layer download timeouts.

Use Clash's connection and DNS logs while running one test at a time. The useful evidence is the requested hostname, the matched rule, the selected proxy group, and whether the connection was direct or proxied. A connection log showing only the browser's request proves nothing about dockerd. For deeper analysis on Linux, capture traffic on the physical interface, Docker bridge, and TUN interface separately:

sudo tcpdump -ni any 'host registry-1.docker.io or port 443'
sudo ss -lntup | grep -E '7890|7891|53'
ip rule
ip route show table all

Never paste subscription URLs, proxy credentials, authorization headers, or private registry tokens into public logs. Redact those values before sharing diagnostic output. A packet capture can also expose destination metadata, so limit captures to a short test window and store them securely.

  • Connection refused: the daemon can reach the address but no service accepts the connection, or the configured local proxy port is wrong.
  • Temporary failure in name resolution: DNS is unavailable, blocked, or pointed at a resolver that the namespace cannot reach.
  • Context deadline exceeded: the request exceeded its timeout; compare DNS, TCP, TLS, and proxy logs to identify which stage waited.
  • Unauthorized: often an expected registry response during a direct /v2/ test, not a transport failure.
  • Manifest unknown: connectivity worked, but the requested image name or tag does not exist.

Use a Stable Production Layout

For a single Linux host that only needs reliable image pulls, configure the Docker daemon with an explicit Clash HTTP proxy, keep Docker's internal networks direct, and use Clash rules for the public registry domains. This design has fewer moving parts than transparent interception and is easier to audit after a reboot. For a host running several services that must all follow the same policy, use mihomo TUN with automatic routes, explicit Docker subnet exclusions, split DNS, and a documented IPv4/IPv6 decision.

Apply changes in a controlled order: save the current Docker service drop-ins, validate the Clash configuration before restarting the core, restart only the affected service, and then run a small test image pull. Watch both the Docker daemon log and Clash connection log. If the host has production workloads, test a disposable container and a private registry path separately from Docker Hub; public registry success does not prove that internal service discovery remains intact.

The final checklist is short but important:

  • Identify whether the request is made by dockerd, a container, or Docker Desktop's VM.
  • Confirm the local Clash listener uses the protocol configured in Docker.
  • Route authentication, registry, and verified layer-delivery domains consistently.
  • Make DNS reachable from the namespace that performs the lookup.
  • Exclude Docker bridge ranges and Clash's own upstream path from routes that would create loops.
  • Test DNS, TCP, TLS, HTTP 401, and the complete pull as separate checkpoints.
  • Keep private registries direct or on their intended corporate proxy path.

Once these layers are separated, a Docker Hub timeout stops being a vague “Clash is not working” problem. It becomes a traceable decision: the daemon lacks a proxy, DNS is leaving through the wrong resolver, a registry hostname is missing from the rules, TUN routing is looping, or the remote node cannot sustain the download. Fix the layer that actually fails, then keep the configuration as narrow as the deployment requires.

Download Clash