How-to

Make the terminal, git, curl and npm use a proxy on macOS

macOS 13+ · Apple Silicon and Intel · Updated 2026-08-30

The advice everyone gives first

Set the environment variables, and a good number of tools will start using your proxy:

~/.zshrc
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:7890
export no_proxy=localhost,127.0.0.1,::1,192.168.0.0/16,*.internal

# some tools only read the uppercase spelling
export HTTP_PROXY=$http_proxy HTTPS_PROXY=$https_proxy NO_PROXY=$no_proxy

Then reload the shell and check. This advice is correct as far as it goes, and for curl and wget it is usually the end of the story:

Terminal
source ~/.zshrc
curl -s https://api.ipify.org; echo    # should show the proxy's address
env | grep -i proxy                    # confirm what is actually exported

Where it stops working

The variables are a convention that each tool opts into separately, so coverage is uneven in ways that are hard to predict. The failures below are the ones that cost people an afternoon:

Tool Reads http_proxy? What it actually wants
curl, wget Yes Nothing more
git (https remotes) Usually git config --global http.proxy ... for reliability
git (ssh remotes) No A ProxyCommand in ~/.ssh/config
npm, pnpm, yarn Partly npm config set proxy and https-proxy
pip Partly pip --proxy or a proxy line in pip.conf
Homebrew Mostly, via curl Casks that use their own downloader can still miss it
Docker CLI vs daemon CLI yes, daemon no Proxy settings inside Docker Desktop itself
ssh, scp, rsync over ssh No ProxyCommand only
Go, Rust, Java tooling Varies by program Often a program-specific flag or config file
Anything using UDP No Not expressible with these variables at all

And there is a second trap that has nothing to do with the tools. Variables exported in your shell profile exist only for processes started from a shell. Launch your editor from Finder, the Dock or Spotlight and it inherits launchd's environment instead — so the integrated terminal inside it may be proxied while the editor's own updater and extension downloads are not.

Fixing it tool by tool

If you only need two or three tools proxied, configuring each one is perfectly reasonable and costs nothing:

Terminal
# git over https
git config --global http.proxy socks5h://127.0.0.1:7890
git config --global --unset http.proxy      # to undo

# npm
npm config set proxy http://127.0.0.1:7890
npm config set https-proxy http://127.0.0.1:7890

# git over ssh, in ~/.ssh/config
Host github.com
  ProxyCommand nc -X 5 -x 127.0.0.1:7890 %h %p

Note socks5h rather than socks5 for git: the h asks the proxy to resolve the hostname, which avoids leaking DNS queries and fixes the case where a name resolves differently on your side of the connection.

Fixing it once, for the whole terminal

The per-tool approach stops scaling the moment a new tool appears, or when a colleague's script fails on your machine for reasons neither of you can see. The alternative is to stop asking each program to cooperate and route the terminal application itself.

Because ProxyRouter decides by process at the network layer, everything the terminal spawns inherits the decision — curl, git, ssh, a Go binary you compiled five minutes ago, a package manager you have never configured. There are no variables to export and nothing for a tool to ignore.

  1. Add your SOCKS5 or HTTP upstream once.
  2. Add a rule for your terminal app — Terminal, iTerm, Ghostty, or your editor if you use its built-in one.
  3. Keep localhost and your LAN ranges on Direct so local servers and containers still answer.
  4. Leave the default action as Direct, so nothing else on the Mac changes.

A useful side effect: because the routing decision no longer lives in your dotfiles, you can drop the proxy exports entirely and stop debugging why a script behaves differently in a login shell, a CI runner and an editor terminal.

Verifying

Terminal
# 1. does the upstream itself work?
curl -s --socks5-hostname 127.0.0.1:7890 https://api.ipify.org; echo

# 2. what does an unconfigured request do?
curl -s https://api.ipify.org; echo

# 3. which tools are configured behind your back?
env | grep -i proxy
git config --get-regexp '^http[s]*\.proxy'
npm config get proxy

If line 1 and line 2 print the same address, the terminal is routed. If they differ, it is not — and with per-app routing you fix that once, in one place, instead of hunting for the tool that did not get the memo.

FAQ

Why doesn't http_proxy work for everything?

Because it is a convention, not a system feature. Every tool decides independently whether to read those variables, which spelling it accepts, and whether it applies them to HTTPS as well. Tools that predate the convention, or that ship their own network stack, simply ignore them.

Why does it work in my shell but not in an app I launched from Finder?

Environment variables set in your shell profile only exist for processes started from that shell. An app launched from Finder, the Dock or Spotlight inherits launchd's environment instead, which never saw your profile — so it sees no proxy variables at all.

Does SSH use http_proxy?

No. SSH ignores those variables entirely. It needs a ProxyCommand in your SSH config, typically using nc or a helper, and that has to be configured per host or globally in ~/.ssh/config.

Is there a way to cover every tool at once?

Yes, but not through environment variables — they will always be opt-in per tool. Routing the terminal application itself at the network layer covers every command it runs, including ones that never heard of http_proxy, and including UDP.

ProxyRouter comes with a free full-feature trial, no card required. You bring your own SOCKS5 or HTTP upstream — a corporate proxy, a staging gateway, or a port on localhost. We do not provide or resell proxy servers.

Download for macOS See pricing

Related