Why Docker transparent proxy routing is harder than setting HTTPS_PROXY
Searches such as Clash Docker transparent proxy, route container traffic through Clash, and Docker TUN proxy usually begin with an apparently simple requirement: a service inside a container must reach an external developer platform, registry, package mirror, or API, but the application does not expose a usable proxy setting. You may already have Clash Verge Rev working on a laptop, or Mihomo running on a gateway, while the container continues to report connection resets, DNS failures, or endless download retries.
The important distinction is that a container is not automatically part of the host application’s proxy session. An exported HTTPS_PROXY variable affects only processes that inherit it. A desktop system proxy may cover browsers and selected applications, but it does not guarantee that Docker bridge traffic, sidecar processes, health checks, or build stages will use the same path. Transparent proxying solves a different problem: it intercepts traffic at the network layer so applications can remain unaware of the proxy.
This guide presents a production-minded design for routing Docker traffic through Clash-compatible Mihomo. It covers the boundaries between Docker networking, TUN interception, iptables or nftables redirection, rule providers, and DNS. The goal is not to make every packet travel through one remote node. The goal is to create an observable policy: local services remain DIRECT, approved external destinations use a proxy group, and DNS decisions are consistent with the routing mode.
Understand the traffic path before changing configuration
Start by drawing the actual path from the container to the destination. In the common Docker bridge model, an application sends a packet from a private container address such as 172.18.0.5. Docker’s virtual Ethernet pair carries the packet to the host bridge, then the host performs network address translation before sending it to the LAN gateway or the public network. A Clash process running only on the desktop host may see some host-originated traffic, but that does not mean every forwarded packet is intercepted.
Four components must agree before transparent routing works reliably:
- Docker network: the bridge, macvlan, host, or custom network determines where packets enter the host and which source addresses are visible.
- Interception method: TUN captures traffic through a virtual interface, while iptables or nftables redirects selected TCP and UDP flows to a local Clash listener.
- Policy engine: Clash rules decide whether a destination is sent to a proxy group, kept DIRECT, rejected, or handled by a special DNS policy.
- Resolver path: DNS must return answers that match the interception strategy. Fake-IP, redirection, and ordinary system resolution can produce very different results.
A useful mental model is to separate forwarding from classification. Docker forwarding answers, “Can this packet leave the container network?” Clash classification answers, “Which policy should handle this domain or address?” If forwarding is broken, adding rules will not help. If forwarding succeeds but classification is wrong, the container may connect through the wrong region, bypass the proxy, or fail only for selected hostnames.
Do not begin with a large YAML file copied from a forum. First identify the container subnet, the host interface, the Clash control mode, the mixed or redirection port, and the destinations that actually fail. These values are environment-specific, and confusing a host address with a container gateway is one of the fastest ways to create a routing loop.
Choose the Docker networking mode deliberately
| Docker mode | Useful property | Main caution |
|---|---|---|
| Bridge | Clear isolation and predictable private subnets | Forwarded packets require explicit interception and return-path testing |
| Host | The container shares the host network namespace | Less isolation; port collisions and host-level policy become important |
| Macvlan | The container appears as a separate LAN device | Host-to-container communication and policy visibility can be surprising |
| Custom bridge | Stable subnet and controlled service grouping | Every custom subnet must be included consistently in firewall rules |
Bridge networking is often the best starting point because it keeps the application isolated and makes the source subnet explicit. Host mode can simplify TUN experiments, but it also removes useful boundaries: a mistake in the transparent proxy policy may affect host services, monitoring agents, and other applications. Macvlan is attractive for network appliances, yet it requires careful attention to how packets reach the host and whether the interception process can observe them.
TUN interception versus iptables and nftables redirection
A Clash TUN interface creates a virtual network device that can receive traffic and route it through Mihomo’s policy engine. This is convenient for applications that ignore proxy environment variables, support UDP, or open connections in ways that are difficult to configure manually. TUN also makes the routing model feel closer to a full device tunnel: applications send ordinary traffic, and the network layer decides what happens next.
However, TUN is not a magic switch inside a container. The process needs the appropriate network capabilities, access to the relevant namespace, and a route design that does not send the proxy’s own upstream connections back into itself. In a Docker deployment, the TUN interface may live in the host namespace while the application lives in a bridge namespace. You must verify that forwarded traffic can reach the interception point and that the reply path returns through the expected interface.
iptables or nftables redirection takes a more selective approach. Firewall rules match traffic from the Docker subnet and redirect TCP connections to Clash’s redirection or TProxy listener. This can be easier to reason about when you want only one Compose project to use the proxy. It also makes exclusions explicit: the Docker DNS resolver, the host gateway, the LAN subnet, and the proxy listener itself can be exempted before a broad redirect rule is applied.
- Prefer TUN when you need broad application compatibility, UDP handling, and a central policy for several traffic types.
- Prefer redirection when you need narrow scope, simple rollback, and a clearly defined TCP workload.
- Consider TProxy when preserving the original destination and handling transparent traffic is important, but expect more demanding routing and kernel configuration.
TCP-only redirection is not automatically wrong. Many registries, REST APIs, and package services work adequately over TCP. Still, modern clients may prefer QUIC over UDP, and some service discovery or media workflows depend on UDP. If an application works in a browser but fails in a container, check whether the browser fell back to TCP while the container attempted UDP that your interception method does not handle.
Hands-on setup: route one Compose service through Mihomo
The safest first experiment is a single disposable service, not the entire Docker host. Create a dedicated bridge network with a predictable subnet, record the host-side gateway, and use a temporary container that can perform DNS and HTTPS tests. Keep the test narrow enough that one change produces one observable result.
- Inspect the Docker network and record its subnet, gateway, and bridge interface. Do not assume the default
docker0values if your Compose project creates a custom network. - Confirm that Mihomo is listening on the intended mixed, redirection, or TProxy port and that its firewall or bind address allows the Docker gateway to reach it.
- Ensure IP forwarding is enabled on the host, then test ordinary DIRECT connectivity from the container before adding interception.
- Add a scoped rule for the container subnet. Exclude private LAN ranges, the Clash listener address, and the DNS endpoint used for bootstrap resolution.
- Start with one known external hostname and inspect Mihomo’s Connections or Logs view. Verify the destination, selected rule, proxy group, and final connection result.
- Only after the test is stable should you attach the policy to additional services or convert the experiment into a persistent firewall configuration.
A conceptual redirection policy may look like this:
# Illustrative values only; replace them for your host
docker_subnet: 172.20.0.0/16
clash_gateway: 172.20.0.1
redirect_port: 7892
dns_port: 1053
The values above are deliberately placeholders rather than a copy-and-run recipe. The correct interface, port, and subnet depend on whether Mihomo runs directly on the host, in a privileged container, or on a separate gateway. Before enabling persistent rules, use packet counters or connection logs to confirm that traffic from the intended subnet actually reaches the redirect chain. If counters remain at zero, the issue is placement or forwarding, not the provider’s domain list.
For a Compose service that supports environment variables, you can still use explicit proxy variables as a complementary measure:
environment:
HTTPS_PROXY: http://host.docker.internal:7890
HTTP_PROXY: http://host.docker.internal:7890
NO_PROXY: localhost,127.0.0.1,.local,172.20.0.0/16
Do not treat this as a replacement for transparent routing. It helps tools such as package managers and Git clients that understand HTTP proxies, while transparent interception covers binaries that do not. The two methods can coexist, but avoid sending an already proxied connection into a transparent redirect unless you understand the resulting path. A proxy variable pointing to the wrong namespace, or to a listener that is reachable only from the host, can create misleading connection-refused errors.
Build maintainable rules with provider-managed destinations
Hard-coded rules are useful for a small test, but production services tend to contact more hosts than their documentation lists. A developer platform may use separate API, authentication, object storage, telemetry, download, and certificate endpoints. A single rule for the visible website can therefore produce a half-working application: the landing page loads, but login or artifact retrieval fails.
Rule Providers let you maintain groups of domain, domain-suffix, IP-CIDR, or classical rules outside the main profile. This keeps the core policy readable and allows a provider list to be updated without rewriting every routing decision. Use providers from sources you trust, pin formats that your Mihomo version supports, and review changes before applying them to a production gateway.
A practical policy order usually follows this sequence:
- Reject explicitly unwanted traffic before broad proxy rules, where appropriate for your environment.
- Bypass localhost, Docker service names, private address ranges, and local control planes.
- Route provider-managed developer service domains through a named proxy group.
- Handle regional media, package mirrors, or vendor-specific domains with separate groups if their egress requirements differ.
- Keep a conservative final rule, such as
MATCH,DIRECTorMATCH,Proxy, only when you understand the privacy and availability implications.
Rule order matters more than the number of rules. A broad GEOIP,CN,DIRECT entry placed above a required developer domain can override your intended proxy decision. Conversely, placing a broad proxy rule first may route local registries, internal dashboards, or database connections through an unnecessary remote node. When debugging, use a temporary explicit hostname rule rather than rearranging the whole profile. Once the connection is proven, move the destination into the appropriate provider set.
Choose proxy groups for failure behavior, not just speed. A URL-test group can select a responsive node, but latency to a small test URL does not guarantee that long-lived API streams or large container downloads will remain stable. A manually selected group may be better for a build pipeline when reproducibility matters. For critical workloads, define a fallback strategy and monitor whether a node change invalidates vendor sessions or triggers rate-limit behavior.
DNS design: the part that makes transparent proxying predictable
DNS is often the reason a transparent proxy appears inconsistent. If the container resolves a domain locally and receives an address that is unreachable from the host’s current route, the later interception rule never gets a chance to help. If Clash uses fake-IP while the application or firewall expects ordinary addresses, logs may show synthetic results that confuse troubleshooting. Neither mode is universally best; the correct choice depends on the application and interception method.
With ordinary redirection, many teams begin with a controlled resolver reachable from the Docker network and let Clash decide how external names are resolved. Keep local service discovery separate. Docker’s internal names, such as a database service name on the Compose network, should not be sent to a public resolver or a remote proxy. Add clear exclusions for internal zones, private ranges, and the hostnames required to bootstrap the proxy provider itself.
Fake-IP can simplify domain-based policy because the connection remains associated with the original hostname inside the Clash ecosystem. It can also confuse software that performs its own certificate pinning, embeds IP literals, validates DNS responses, or expects a real address for local network operations. Test package managers, container registries, database clients, and health checks separately instead of assuming that one successful browser request proves DNS compatibility.
When a request fails, compare three observations: the DNS answer seen inside the container, the hostname or IP shown in Mihomo Logs, and the rule selected for the connection. A mismatch between those observations is more useful than repeatedly changing proxy nodes. Also check IPv6. A container may receive an AAAA record and attempt IPv6 while your transparent rules cover only IPv4, producing failures that look random because the application eventually falls back to IPv4.
Production checks, security boundaries, and rollback
Transparent proxying changes the trust boundary of the host. A privileged Clash container with access to host networking or firewall administration can affect every workload on that machine. Use the least privilege that supports your chosen interception method, bind administrative APIs to a protected interface, and never expose the Clash controller port to the public Internet. Subscription URLs and provider tokens should be treated like credentials: keep them out of images, Git repositories, public issue reports, and debug logs.
Separate test and production profiles. A test profile can use a temporary rule, verbose logging, and a manually selected node. A production profile should use version-controlled configuration, documented provider sources, predictable group names, and a rollback copy. Apply firewall changes in a way that preserves an existing management path; otherwise, a syntax error can disconnect the very SSH session you need to repair the host.
Monitor more than whether the container is “up.” Useful checks include DNS resolution, HTTPS handshake time, response status, download throughput, connection reuse, and the proportion of DIRECT versus proxy traffic. A health check that only verifies the process port may remain green while every external API call is failing. For long-running services, watch memory usage and connection counts in Mihomo, especially after provider updates or node rotation.
Use a repeatable troubleshooting order:
- Test the application without interception to establish whether the destination is reachable at all.
- Test DNS from inside the same container and compare IPv4 and IPv6 behavior.
- Confirm the container subnet and firewall counters.
- Inspect the Mihomo connection log for the original destination and selected rule.
- Temporarily switch one destination to Global or a known-good proxy group.
- Restore rule-based routing and correct the narrowest faulty layer.
If disabling transparent routing immediately restores local services, look for an overbroad redirect or a missing private-range bypass. If only one provider-managed group fails, check its format, refresh time, and compatibility with the current Mihomo build. If all external destinations fail, inspect the listener bind address, route table, upstream node health, and the possibility of a loop where Clash’s own outbound connection is captured by its own redirect rules.
Compared with application-by-application proxy settings, which are easy to forget during image updates, or consumer VPN containers that often provide little visibility into rule selection and DNS behavior, Clash offers a more inspectable model: Docker traffic can be scoped by subnet, destinations can be maintained through Rule Providers, TUN or firewall interception can be selected per workload, and Mihomo Logs can show whether a request was DIRECT or proxied. If you need that combination of container-wide coverage and fine-grained routing rather than another opaque tunnel, the next practical step is to install the Clash client that matches your host or gateway and validate the policy with one service at a time.