Why developers need more than a browser proxy

If you searched for Clash TUN mode Git proxy, npm install behind Clash, or Homebrew connection failed with proxy, you have probably encountered a familiar developer workstation problem: the browser works, but the terminal does not. Git may time out while GitHub opens normally, npm install may fail to resolve a registry, pip may repeatedly retry package downloads, and Homebrew may report that it cannot fetch a bottle even though ordinary web pages load without trouble.

The underlying issue is usually not that every tool needs a different proxy product. It is that command-line programs do not all inherit the same network settings. Some read HTTP_PROXY and HTTPS_PROXY; some require lowercase variables; some implement their own proxy options; some open direct sockets; and Docker often runs in a separate daemon or virtual machine. A setting enabled in a browser or desktop client therefore does not automatically cover the complete developer workflow.

This guide uses Clash TUN mode as the common traffic layer. Instead of teaching every application how to speak to a local HTTP or SOCKS port, TUN creates a virtual network interface and allows Mihomo, the core used by many modern Clash clients, to capture traffic from applications that ignore ordinary proxy variables. You will still configure explicit variables where they improve predictability, but TUN becomes the safety net for Git helpers, language runtimes, package managers, container processes, and other terminal tools.

The goal is not to route every connection through a remote node without thinking. A reliable developer setup separates public services that need the proxy, private repositories that must remain local, corporate domains that should use an internal gateway, and ordinary local traffic that should stay DIRECT. Good rules reduce latency, protect credentials, and make failures easier to diagnose.

Prepare Clash and validate TUN mode safely

Start with a current Clash client that supports Mihomo and TUN mode, such as Clash Verge Rev or another maintained desktop client for your operating system. Import a profile from a provider or an infrastructure service that you are authorized to use. A subscription supplies nodes and policy groups; it does not automatically guarantee that every developer endpoint will be routed correctly. Keep the profile in a private location, and never publish a subscription URL containing a live token in a shell history, issue, or screenshot.

Before enabling TUN, identify the local ports shown by your client. A typical profile exposes an HTTP mixed port such as 7890, although the actual value may differ. TUN mode normally requires permission to create a virtual interface or install a system helper. Approve that request only when the application and component name match the client you intentionally installed. On macOS, you may see a network extension or administrator prompt; on Windows, the client may request permission for a virtual adapter; on Linux, it may need access to the TUN device and routing table.

Use this order when testing:

  1. Confirm that the Clash dashboard can reach its controller and that at least one proxy group has a healthy node.
  2. Enable system proxy temporarily and test a simple browser request through the selected group.
  3. Enable TUN mode, allow the requested operating-system permission, and wait for the virtual interface to become active.
  4. Open the Mihomo Connections or Logs view and generate traffic from a terminal so you can see the actual hostname, policy, and final connection type.
  5. Test one public service, one local service, and one private or corporate hostname before changing large numbers of rules.

TUN is powerful because it operates below the application layer, but it is not magic. DNS behavior, IPv6, certificate validation, and process privileges can still affect the result. If a terminal command works with a direct HTTP proxy but fails with TUN, compare the connection log rather than switching randomly between profiles. Look for whether the request is visible, whether the hostname resolves to a fake IP, which rule matched, and whether the selected node completed the TLS handshake.

Many profiles use fake-IP DNS to ensure that domain-based rules can be applied consistently. That can confuse local development environments that expect a real LAN address, a loopback hostname, or a service-discovery name. Add local zones to the profile’s DNS or routing exclusions according to your environment. Common examples include localhost, *.local, private address ranges, internal service suffixes, and the hostnames used by local Kubernetes or Docker development. Do not exclude an entire private network blindly if your organization intentionally routes some internal services through a secure gateway.

A useful baseline is to keep local and private destinations direct while routing selected public developer services through a proxy group. In rule-based mode, put specific domain rules before broad regional or final rules. A broad GEOIP or FINAL rule placed too early can capture GitHub, package registries, or an internal mirror before your intended policy gets a chance to match.

Make Git and SSH predictable from the terminal

Git traffic commonly fails in two different ways. HTTPS remotes use an HTTP-style connection and can often follow Git’s proxy configuration or the shell environment. SSH remotes use a separate protocol and usually do not understand an HTTP proxy directly. Treating both URLs as “Git traffic” hides an important implementation difference.

For an HTTPS repository, first confirm the remote:

git remote -v
git config --global --get http.proxy
git config --global --get https.proxy

If you want explicit Git behavior, point Git at the local mixed port exposed by Clash. Replace the port with the value shown by your client:

git config --global http.proxy http://127.0.0.1:7890
git config --global https.proxy http://127.0.0.1:7890

This configuration is useful when TUN is unavailable, when a particular Git process runs outside the TUN interface, or when you want an easily auditable application-level setting. However, do not leave stale proxy values behind after changing clients or ports. A common diagnostic mistake is to enable TUN while Git still points at a closed port from an old VPN application. Check and remove obsolete values with:

git config --global --unset http.proxy
git config --global --unset https.proxy

With TUN active, many users can leave Git’s explicit proxy unset and let domain rules decide. This avoids hard-coding a local port in shared dotfiles, especially on machines where the Clash port differs between operating systems. Test the actual path using a small repository operation rather than a large clone:

git ls-remote https://github.com/example/project.git
git clone https://github.com/example/project.git

For SSH remotes such as [email protected]:owner/project.git, configure an SSH transport deliberately. One approach is to use a local SOCKS port and an appropriate ProxyCommand. The exact command depends on your OpenSSH build and whether your Clash client exposes a SOCKS listener. A typical pattern is:

Host github.com
    HostName github.com
    User git
    ProxyCommand connect -S 127.0.0.1:7891 %h %p

Do not copy this blindly if the connect utility is not installed or if your client uses a different port. Another option is to use HTTPS remotes for repositories where SSH is not required. Whichever route you choose, verify it with ssh -T [email protected] and inspect the Clash log. Authentication errors and routing errors look different: a successful TCP path followed by “permission denied” points to keys or account access, not TUN.

Private repositories require additional care. Public GitHub traffic may need a proxy while your company’s GitLab, Bitbucket Server, or code forge must remain on an internal network. Add exact internal domains and private address ranges to a DIRECT or corporate policy group, then place those rules before a broad GitHub rule. Also consider package registries, artifact storage, and SSO endpoints used by the private forge. A repository clone can appear to fail at GitHub while the real problem is an OAuth redirect or a private submodule hostname that followed the wrong policy.

Route npm, pip, and Homebrew without breaking local mirrors

Package managers are especially sensitive to inconsistent routing because one command can contact several hosts. npm may reach the configured registry, authentication endpoints, tarball hosts, and Git dependencies. pip may use an index, a trusted host, a wheel mirror, and a source archive server. Homebrew may contact GitHub repositories, release storage, bottle mirrors, and formula metadata services. Routing only the first hostname creates the impression of random failure.

Check npm’s current state before adding new variables:

npm config get registry
npm config get proxy
npm config get https-proxy

If your policy allows public npm traffic through Clash, you can use a temporary command-level proxy rather than permanently changing every project:

npm --https-proxy=http://127.0.0.1:7890 install
npm view typescript version

For a persistent user-level setting, use npm’s configuration commands and document the choice for your team. Be careful with credentials: npm configuration files can contain registry tokens, and printing them in a support log may expose access to private packages. If your organization provides an internal registry, keep its hostname on the intended internal route and use scoped packages such as @company/* with the matching registry configuration.

pip supports environment variables and command options, but the best setting depends on whether you use a public index or an approved mirror. A temporary test looks like this:

HTTPS_PROXY=http://127.0.0.1:7890 \
HTTP_PROXY=http://127.0.0.1:7890 \
pip install requests

Do not add --trusted-host simply because a proxy is involved. That option changes TLS verification behavior and can weaken transport security. If a private index uses an organization certificate, install the approved CA through the operating system or Python environment according to your security team’s instructions. A certificate error should be investigated as a trust-store issue, not “fixed” by disabling verification.

Homebrew is often a good TUN test because it combines GitHub access with binary downloads. Run a diagnostic command first:

brew update
brew config
brew doctor

If Homebrew sees the network but downloads fail, inspect the hostnames in the Clash Connections view. A formula may be fetched from GitHub while its bottle comes from a release or package storage domain. Add rules based on observed destinations, not on a copied list that may be outdated. If you use a regional or corporate mirror, set it explicitly and route that mirror direct or through the required gateway. Avoid mixing a mirror’s environment variables with a public registry policy until you know which source each command will contact.

The practical rule is to use TUN for coverage, explicit package-manager settings for reproducibility, and application-specific mirrors for supply-chain control. These layers should agree. If npm points to an internal registry while Clash sends the internal domain through a public proxy, or if pip uses a mirror excluded by NO_PROXY, the resulting behavior will be confusing even though each individual setting looks reasonable.

Align shell variables, containers, and development services

Shell variables remain valuable even when TUN is enabled. They make subprocess behavior visible and help tools that do not participate correctly in transparent interception. Set uppercase and lowercase forms when a tool’s documentation is unclear:

export HTTP_PROXY=http://127.0.0.1:7890
export HTTPS_PROXY=http://127.0.0.1:7890
export ALL_PROXY=socks5://127.0.0.1:7891
export http_proxy="$HTTP_PROXY"
export https_proxy="$HTTPS_PROXY"
export all_proxy="$ALL_PROXY"

Use NO_PROXY to protect local development and internal destinations that should not leave the machine or LAN:

export NO_PROXY=localhost,127.0.0.1,::1,.local,.internal
export no_proxy="$NO_PROXY"

The leading dot convention is widely used for subdomains, but support varies by application. Validate with the tool you care about instead of assuming every runtime interprets the list identically. Do not include an entire public domain in NO_PROXY just to solve one failing endpoint; narrow exclusions are easier to audit.

Docker introduces a boundary that frequently surprises developers. A container may not see the host’s 127.0.0.1 as the host machine, and a Docker daemon may perform image pulls independently of the shell that launched docker build. On Docker Desktop, configure proxy behavior in the application’s supported settings and restart the relevant engine when required. For containers that must reach a host-side Clash listener, use the host gateway address supplied by your platform rather than assuming loopback will work.

For a build that needs package downloads, pass only the necessary proxy arguments and avoid baking secrets into image layers:

docker build \
  --build-arg HTTP_PROXY=http://host.docker.internal:7890 \
  --build-arg HTTPS_PROXY=http://host.docker.internal:7890 \
  -t developer-test .

BuildKit may provide separate configuration mechanisms, and Linux hosts can require a real bridge or LAN address instead of host.docker.internal. Treat the example as a diagnostic starting point, not a universal production configuration. If the image contains private dependencies, use short-lived credentials, secret mounts, or an approved internal mirror. Never place a subscription token or package registry password in a Dockerfile, command copied into shell history, or public CI log.

Internal services need an explicit policy boundary. Keep local databases, test APIs, service meshes, Kubernetes control planes, and corporate SSO on the route required by your organization. If a development hostname resolves differently under fake-IP DNS, add a targeted hosts or DNS rule and confirm the result from both the host and the container. A successful browser request from the host does not prove that a container has the same resolver, certificate store, or network namespace.

A repeatable troubleshooting and maintenance workflow

When a command fails, avoid changing five variables at once. First record the exact command, hostname, protocol, operating system, and whether the failure occurs with TUN disabled, TUN enabled, or an explicit application proxy. Then reproduce with a small request and watch the Clash log. The log should answer four questions: did the connection enter Clash, which rule matched, which policy group was selected, and did the remote handshake complete?

  • No connection appears: the application may bypass TUN, use a separate container or daemon, fail before networking, or be blocked by a local permission issue.
  • The wrong rule matches: reorder domain rules, add the actual observed hostname, or review fake-IP and DNS behavior.
  • The correct rule matches but the request times out: test another node, inspect latency and saturation, and compare TCP and UDP behavior.
  • The request completes but authentication fails: stop changing routing and verify tokens, SSH keys, registry scopes, certificates, and account permissions.
  • Only large downloads fail: investigate node stability, MTU, connection reuse, range requests, and timeouts rather than assuming DNS is broken.

Use low-risk probes that reveal different layers:

curl -I https://github.com
git ls-remote https://github.com/example/project.git
npm view npm version
python -m pip index versions requests
docker pull hello-world

Run each command separately and note the destination shown in Clash. A browser success is useful only as a comparison; it does not establish that Git, Node.js, Python, or Docker uses the same resolver and proxy path. Likewise, a temporary Global mode can be a diagnostic control, but it should not automatically become the permanent policy. If Global mode fixes the issue, return to Rule mode and identify the missing or misordered rule.

Review the setup after client upgrades, subscription refreshes, operating-system updates, and changes to internal infrastructure. Node names, package mirrors, OAuth hosts, and container networking conventions change over time. Keep a short project note describing which domains are public, which are internal, which ports the local client exposes, and which variables CI uses. That documentation is more useful than a large undocumented YAML file copied from a different workstation.

Compared with lightweight browser-only proxy extensions, a TUN-capable Clash workflow covers terminal programs that never expose a proxy setting, while still allowing precise DIRECT rules for private repositories and internal services. Compared with forcing every tool through separate hard-coded variables, it reduces port drift and makes Git, package managers, and containers visible in one connection log; compared with an all-or-nothing VPN, rule-based routing preserves local access and makes the policy auditable. If you want that combination of transparent coverage and deliberate developer routing, choose the client build that matches your platform and continue with a controlled Clash setup.

Download Clash for free and browse freely →