Why Terminal ignores the proxy that Clash already exposes
On macOS, “Set as system proxy” in a Mihomo-based GUI usually configures the CFNetwork stack for apps that honor the system configuration. Safari, many Electron clients, and parts of the App Store read that path. A new Terminal window, however, starts as a fresh process with only the environment variables your shell profile exports. Unless something explicitly copies the system proxy into HTTP_PROXY or you rely on TUN mode to capture packets at the kernel layer, command-line tools default to a direct route.
curl is a good example: it respects uppercase and lowercase proxy variables when they exist, but it does not magically query macOS proxy settings on every invocation. git uses its own configuration keys unless the environment overrides them. npm reads .npmrc and may ignore shell exports for registry traffic if you previously ran npm config set with empty values. The fix is deliberate: align every layer on the same listener URL, typically the mixed inbound that speaks HTTP CONNECT for HTTPS and plain HTTP in one port.
If you have never confirmed which port your profile actually opens, skim the Clash Verge Rev setup guide first. The numbers below assume 7890 for mixed; substitute the value shown in your YAML or tray UI everywhere.
Step 1: Read the mixed port from Clash (not a guess)
Open your client’s settings or the raw config and locate the mixed-port field (or separate port for HTTP if you do not use mixed). Mixed mode is convenient because one URL feeds both http:// and https:// clients that speak HTTP CONNECT. If your build exposes SOCKS on a different port, you can still set ALL_PROXY to a socks5h:// URL, but beginners see fewer surprises when everything goes through the mixed HTTP listener.
Ensure Clash is running and the inbound is bound to an address your shell can reach. The default 127.0.0.1 is correct for local Terminal use. If you later proxy Docker containers or LAN devices, you may need allow-lan and 0.0.0.0 as described in Enable Clash LAN proxy on Windows and macOS; that article complements this one when the consumer is not localhost.
Quick sanity check before touching shell files:
curl -I --proxy "http://127.0.0.1:7890" https://www.google.com
Swap the port if needed. Headers should return quickly; immediate failures mean the listener is down, blocked by firewall, or the port number is wrong.
Step 2: Export proxy variables for the current shell session
Most Unix tools look for uppercase variables; some also honor lowercase duplicates. Setting both avoids odd behavior in Node-based CLIs. A typical pattern for zsh (default on modern macOS) looks like this:
export HTTP_PROXY="http://127.0.0.1:7890"
export HTTPS_PROXY="http://127.0.0.1:7890"
export ALL_PROXY="http://127.0.0.1:7890"
export http_proxy="$HTTP_PROXY"
export https_proxy="$HTTPS_PROXY"
export all_proxy="$ALL_PROXY"
export NO_PROXY="localhost,127.0.0.1,::1"
export no_proxy="$NO_PROXY"
HTTPS_PROXY does not require an https:// scheme in the value when the proxy itself is an HTTP proxy that upgrades via CONNECT—pointing at http://127.0.0.1:7890 is normal. ALL_PROXY catches tools that only read that key; leaving it unset is a common reason some binaries still go direct while curl works.
NO_PROXY lists hosts that should bypass the tunnel—local registries, company Git servers, or *.local names. Expand the list with comma-separated suffixes such as .corp.example.com so internal endpoints never leave your LAN.
Step 3: Verify with curl and environment visibility
After exporting, run:
curl -v https://example.com 2>&1 | head -n 30
You should see log lines mentioning CONNECT through 127.0.0.1:7890. If curl connects directly without CONNECT, your variables are not visible in that shell—perhaps you edited one terminal tab but ran the command in another, or you use a multiplexer session started before the exports.
To confirm what the shell actually sees:
env | grep -i proxy
Empty output means nothing is exported yet. Remember that subshells inherit the parent’s environment, but sudo often strips variables unless configured otherwise; prefer testing as your normal user first.
Step 4: Persist in ~/.zshrc with a simple on/off toggle
Pasting exports at the bottom of ~/.zshrc works, but toggling Clash off would leave broken connectivity. Wrap the block in a function or guard on a variable you can flip:
# Clash mixed port — change 7890 if your profile differs
CLASH_MIXED_PORT="${CLASH_MIXED_PORT:-7890}"
export CLASH_HTTP_PROXY="http://127.0.0.1:${CLASH_MIXED_PORT}"
function clash_proxy_on() {
export HTTP_PROXY="$CLASH_HTTP_PROXY"
export HTTPS_PROXY="$CLASH_HTTP_PROXY"
export ALL_PROXY="$CLASH_HTTP_PROXY"
export http_proxy="$HTTP_PROXY"
export https_proxy="$HTTPS_PROXY"
export all_proxy="$ALL_PROXY"
export NO_PROXY="localhost,127.0.0.1,::1"
export no_proxy="$NO_PROXY"
echo "Clash proxy env enabled -> $HTTP_PROXY"
}
function clash_proxy_off() {
unset HTTP_PROXY HTTPS_PROXY ALL_PROXY http_proxy https_proxy all_proxy NO_PROXY no_proxy
echo "Clash proxy env cleared"
}
Open a new terminal and run clash_proxy_on after Clash starts. When you disconnect, run clash_proxy_off. Advanced users sometimes detect the listener with a one-liner, but explicit functions fail less mysteriously on laptops that sleep or change networks.
If you use bash instead of zsh, move the same block to ~/.bash_profile or ~/.bashrc according to how you launch Terminal.
Step 5: Git—environment versus git config
Git respects HTTP_PROXY and HTTPS_PROXY for many operations, which is why the exports above often fix git clone immediately. Some teams still set persistent keys:
git config --global http.proxy "http://127.0.0.1:7890"
git config --global https.proxy "http://127.0.0.1:7890"
SSH remotes ([email protected]:...) do not use HTTP proxies; they need ~/.ssh/config ProxyCommand with something like nc -X connect -x 127.0.0.1:7890 or a wrapper, which is outside HTTP env vars. For https:// and http:// remote URLs, the mixed port is enough once Git sees the proxy.
To remove stored proxy keys later:
git config --global --unset http.proxy
git config --global --unset https.proxy
Step 6: npm—registry traffic and corporate registries
npm picks up HTTP_PROXY / HTTPS_PROXY for default registry downloads, but explicit config wins. Inspect current values:
npm config get proxy
npm config get https-proxy
If they print null, rely on environment variables. If someone set them to empty strings, fix or delete:
npm config delete proxy
npm config delete https-proxy
To pin npm to the same mixed port regardless of shell:
npm config set proxy "http://127.0.0.1:7890"
npm config set https-proxy "http://127.0.0.1:7890"
Add your internal registry hostname to NO_PROXY so npm publish to a private Verdaccio host does not round-trip through a public node. For containerized Node workflows on the same Mac, see Docker Desktop on macOS with host Clash, which reuses the same proxy idea with host.docker.internal.
Step 7: When to prefer socks5h:// in ALL_PROXY
Some CLI tools only implement SOCKS. Clash usually offers SOCKS on the same mixed port or a sibling port depending on profile. If you must use SOCKS, prefer the socks5h:// scheme so DNS resolution happens through the proxy, which avoids leaks when local resolvers cannot see the right answers.
Example:
export ALL_PROXY="socks5h://127.0.0.1:7891"
Match the port to your actual SOCKS listener. Mixing HTTP for HTTPS_PROXY and SOCKS for ALL_PROXY can confuse a minority of programs; when in doubt, standardize on the mixed HTTP port for developer tools on macOS.
Homebrew, pip, Go modules, and Rust crates
Once HTTPS_PROXY and ALL_PROXY are set in the shell where you run installers, most ecosystem tools follow without extra flags. Homebrew uses curl under the hood for bottles and formulae sources, so the same exports usually unblock brew update and brew install. If a corporate network injects custom roots, you may still need to trust those certificates in the macOS keychain; proxy reachability and TLS trust are separate problems.
pip honors standard proxy variables for pip install against PyPI. When a project pins an index URL inside a config file, confirm that host is not listed in NO_PROXY by mistake. Go module downloads respect HTTPS_PROXY and GOPROXY defaults; if you set a direct module proxy on your LAN, add it to NO_PROXY so fetches stay internal. Rust cargo reads the same environment keys for crates.io traffic.
IDE-integrated terminals deserve a mention: VS Code, JetBrains, and Cursor often spawn shells that load ~/.zshrc, but tasks launched by the GUI before your profile runs may miss exports. When a build task fails while an interactive tab works, open the integrated terminal, run clash_proxy_on or your equivalent, and retry—or configure the IDE’s own HTTP proxy field to 127.0.0.1:7890 so background language servers and extension marketplaces align with Clash without duplicating logic.
Troubleshooting: common failure modes
Connection refused on 127.0.0.1
Clash is not running, or the mixed port differs from your exports. Re-read the config and retry the explicit curl --proxy test.
Proxy works for curl but not npm
Stale npm config entries or a project-level .npmrc override. Run npm config list -l and search for proxy keys.
Git HTTPS works; SSH still hangs
SSH uses a different code path. Switch the remote to HTTPS or add ProxyCommand for SSH.
DNS resolves but TLS fails
Align Clash DNS mode with your rules. Stubborn cases belong in the Clash troubleshooting guide after you confirm plain proxy tests succeed.
When environment variables are not enough
Some binaries ignore all standard variables. TUN mode routes traffic at the OS level so those programs inherit policy without per-tool configuration. It is heavier-handed and may interact with corporate VPNs, so many developers prefer explicit env vars for day-to-day coding and reserve TUN for stubborn apps.
Launch daemons, cron jobs, and launchd plist tasks do not read your interactive zsh profile. If you must proxy those, inject the same key-value pairs into the service environment or wrap the command in a small script that exports variables first. Otherwise you will see “works in Terminal, fails overnight” reports that are really two different execution contexts.
Summary
macOS system proxy settings and Clash tray toggles help GUI apps first. Command-line tools need explicit HTTPS_PROXY, HTTP_PROXY, and usually ALL_PROXY aimed at your Clash mixed port, plus a thoughtful NO_PROXY list. Verify with curl -v, then align Git and npm so clones and installs follow the same path. Compared with juggling multiple VPN clients, keeping one Mihomo profile and a pair of shell functions gives you predictable behavior every time you open Terminal.
When you want that workflow without hunting release artifacts across the web, the official distribution keeps macOS builds and updates in one place while you focus on exports and configs. → Download Clash for free and experience the difference