Three developer tools, three different proxy settings, and one of them is a trap: the option that looks correct in git does nothing at all, and it fails silently. This guide gives the working configuration for each, the errors they produce when it goes wrong, and how to prove the proxy is actually being used.
Everything below was produced by pointing each setting at a port that refuses connections. If the command fails, the setting was honoured. If it succeeds, the tool ignored the setting and went direct. Each tool has a passing no-proxy control, so a failure means the proxy rather than a broken test.
#The short answer
| Setting | git 2.50.1 | pip 25.0 | npm 11.6.3 |
|---|---|---|---|
https_proxy / HTTPS_PROXY |
Honoured | Honoured | Honoured |
http_proxy / HTTP_PROXY |
Ignored | Ignored | Ignored |
ALL_PROXY / all_proxy |
Honoured | Not tested | Not tested |
| The tool’s own option | http.proxy |
--proxy |
--proxy |
An https variant of that option |
Ignored | None exists | --https-proxy works |
no_proxy bypass |
Honoured | Honoured | --noproxy |
Two rows are worth pausing on. Every one of the three ignores http_proxy when the endpoint is HTTPS, which is correct behaviour and still surprises people who set only that one. And the fifth row is where git and npm disagree completely.
#git: http.proxy is the key, even for HTTPS
This is the mistake that costs the most time, because the wrong configuration reports success.
# Works. Applies to https:// remotes as well.
git config --global http.proxy http://gateway.example:8000
# Does nothing at all. No error, no warning, traffic goes direct.
git config --global https.proxy http://gateway.example:8000
Tested by pointing each at a closed port. With http.proxy the clone failed, which proves git tried to use it. With https.proxy the clone succeeded — git ignored the setting entirely and connected straight to the remote.
The reason is that git has no such key. Its proxy configuration is http.proxy regardless of the URL scheme, plus the URL-scoped form http.<url>.proxy for per-host rules. Writing https.proxy creates a config entry that nothing ever reads, and git config --list will happily show it back to you.
Per-host configuration uses the same key with a URL in the middle:
# Only this host goes through the proxy
git config --global http.https://github.com/.proxy http://gateway.example:8000
# Everything except this host
git config --global http.proxy http://gateway.example:8000
git config --global http.https://internal.example/.proxy ""
The empty string in the second form is the documented way to say “no proxy for this one”, and it is more reliable than no_proxy because it lives with the rest of your git configuration rather than in the shell that happened to start the command.
#npm: the opposite convention
npm accepts both spellings, and both work:
npm config set proxy http://gateway.example:8000
npm config set https-proxy http://gateway.example:8000
Both were confirmed against a closed port, and both failed as expected. So the option that is a no-op in git is functional in npm. If you carry a mental model from one tool to the other, this is where it breaks.
npm spells its exemption list differently too. It is noproxy, one word, not no_proxy:
npm config set noproxy registry.internal.example,localhost
One caution when testing npm: it answers from its cache. A command that succeeds through a deliberately broken proxy may simply have avoided the network. Point --cache at a fresh temporary directory when you are checking proxy behaviour, or you will measure the cache instead.
#pip
pip is the most conventional of the three. It reads the environment, and it has one flag:
# Per command
pip install --proxy http://gateway.example:8000 requests
# Persistent, in pip.conf
[global]
proxy = http://gateway.example:8000
Both the environment variables and --proxy were confirmed working. no_proxy is honoured, and it needs both hosts when you exempt PyPI, because packages are downloaded from a different domain than the index:
export no_proxy="pypi.org,files.pythonhosted.org"
Exempting only the first leaves every actual download still going through the proxy, which is the kind of half-configuration that produces an intermittent failure rather than an obvious one.
#Reading the errors
The three tools differ enormously in how clearly they report a proxy failure. Each line below was produced deliberately, with the proxy pointed at a closed port or an unresolvable host.
| Tool | What it prints | Does it name the proxy? |
|---|---|---|
| git, port closed | fatal: unable to access ... Failed to connect to 127.0.0.1 port 9 |
Shows the address |
| git, host unresolvable | fatal: ... Could not resolve proxy: no-such-proxy.invalid |
Yes, explicitly |
| npm | npm error code ECONNREFUSED / syscall connect |
Implies it |
| pip | ERROR: Could not find a version that satisfies the requirement six |
No. It blames the package. |
The pip row is the one to remember. A broken proxy produces a message that reads exactly like a package that does not exist, followed by No matching distribution found. Nothing in the output mentions a proxy, a connection or a network. People spend real time checking spelling and package availability before suspecting the thing that is actually wrong.
The rule that saves the time: if a package manager says a package cannot be found, and you are behind a proxy, test the proxy before you test the package name.
#Proving the proxy is applied
Do not infer this from the absence of errors. A tool that ignores your setting succeeds quietly, which looks identical to a tool that used the proxy correctly.
The reliable test is to point the setting at a port where nothing is listening. A configuration that is being honoured must fail:
# If these succeed, the setting is being ignored
https_proxy=http://127.0.0.1:9 git ls-remote https://github.com/octocat/Hello-World.git
https_proxy=http://127.0.0.1:9 pip download six --no-deps -d /tmp/x
https_proxy=http://127.0.0.1:9 npm view express version
Run the same three commands with the variable unset first. If they fail then too, you have a network problem rather than a proxy result, and every conclusion you draw from the test would be wrong.
For a stronger check, run a proxy that logs what it is asked for and confirm the connection appears. A tool that never contacts the proxy leaves nothing in that log, whatever its exit code says.
#Where each setting is stored
Once you stop passing the proxy per command, it has to live in a file, and each tool keeps its own. Knowing which file also tells you where a stale setting will be hiding when the proxy changes.
| Tool | Per-user file | How to find it |
|---|---|---|
| git | ~/.gitconfig, or $XDG_CONFIG_HOME/git/config |
git config --global --list --show-origin |
| npm | ~/.npmrc |
npm config get userconfig |
| pip | ~/.pip/pip.conf or ~/.config/pip/pip.conf |
pip config debug |
Each also has a system-wide layer above the per-user one — /etc/gitconfig, an npmrc beside the npm installation, and a global pip.conf. On a machine somebody else set up, a proxy you cannot find in your own files is usually sitting in one of those.
The commands in the third column are worth running before you debug anything else. --show-origin and pip config debug both report which file a value came from, which turns “why is it still using the old proxy” into a one-line answer.
#Keep the password out of the file
A proxy URL with inline credentials leaks in more places than people expect. In these three tools specifically it ends up in git config --list output, in ~/.npmrc as plain text, and in any CI log that echoes the environment.
# Avoid: the password is now in a file, in shell history, and in --list output
git config --global http.proxy http://user:[email protected]:8000
Where the provider supports it, prefer an address allow-list so no secret travels with the request at all. Where credentials are unavoidable, keep them in an environment variable supplied at run time rather than written into a config file, and make sure whatever prints your configuration for debugging redacts it.
#Where none of this reaches
All three tools read the environment of the process that runs them. That means the settings apply only where they were inherited, and three boundaries commonly break them.
- A container does not inherit your shell. Variables exported in your terminal are not passed to
docker rununless you name them, and a build runs each step in its own container again. Pulling the image is a third case, made by the daemon rather than your shell. - A service manager does not inherit your login shell. A unit started by systemd, or a job started by cron, gets the environment it is given. Exporting a variable in a shell profile and restarting the service changes nothing.
- CI runners start clean. The proxy that works on your laptop is absent in the pipeline unless the pipeline sets it, which is why “works locally, fails in CI” is the standard symptom.
For the general behaviour of these variables across runtimes, including the case rule that makes uppercase HTTP_PROXY unreliable, see the environment-variable guide. The authentication notes there apply here too: a proxy URL containing a password ends up in shell history, in git config --list output and in CI logs.
#How this was tested
Each setting was pointed at http://127.0.0.1:9, a port that refuses connections, so that a successful command proves the setting was skipped and a failed one proves it was attempted. Every tool was given a control run with no proxy set; where that control failed, the results were discarded rather than reported.
That control mattered. Two earlier versions of this test produced a clean-looking table in which every setting appeared to be honoured, and both were wrong: in one the test harness was failing before the tool ran, and in the other the shell was not splitting the command as intended. In both cases the failure looked exactly like a proxy being used. The tables above come from the run where all three controls passed.
Versions: git 2.50.1, pip 25.0, npm 11.6.3, on macOS, on 1 September 2026. Behaviour changes between releases, so run the closed-port check against your own versions rather than trusting a table.
Docker is deliberately absent. We could not run a daemon on the test machine, and we do not publish configuration we have not executed.