๐ŸŽ‰ Premium Proxies ยท 24-Hour Free TrialClaim Now
Guides

pip install Proxy Setup: --proxy, pip.conf and Env Vars

pip install proxy setup that works: the --proxy flag, pip.conf, PIP_PROXY vs HTTPS_PROXY precedence, encoded passwords, and the SSL error a wrong scheme causes.

S SparkProxy 1 21 min read
Share

A pip install proxy problem usually shows up as a wall of Retrying (Retry(total=4...)) warnings followed by No matching distribution found, even though the package obviously exists. pip has three places to take a proxy from (the --proxy flag, its config file, and environment variables), and the way they override each other is the cause of most "I set it and nothing changed" reports. This guide reads the actual error, then gives you each configuration method, the precedence we measured on pip 26.2.2, credentials that survive special characters, and a check that proves pip's traffic actually reaches the proxy.

The behaviour below was checked against the pip 26.2.2 source (released to PyPI on 2026-10-20) and the official pip documentation, and the precedence rules were tested by us on 2026-10-30 against local logging proxies that record every CONNECT request pip sends.

Diagnose From the Retry Warning, Cheapest Check First

The final ERROR line is useless. When every index request fails, pip simply finds zero candidates and reports:

ERROR: Could not find a version that satisfies the requirement six==1.17.0 (from versions: none)
ERROR: No matching distribution found for six==1.17.0

The real diagnosis sits in the WARNING: Retrying ... lines above it, inside the text after connection broken by. These are the strings we captured from pip 26.2.2 on 2026-10-30, one per failure mode:

Text after "connection broken by"What it meansWhere to look
`ConnectTimeoutError(... 'Connection to pypi.org timed out.')` with no proxy host namedpip is not using any proxy and has no direct route[Config methods](#proxy-flag)
`NewConnectionError("HTTPSConnection(host='127.0.0.1', port=28099): Failed to establish a new connection: ... actively refused it")`pip is using a proxy, but nothing listens on that host and portProxy address or firewall
`OSError('Tunnel connection failed: 407 Proxy Authentication Required')`The proxy is reached and rejects the credentials[Password encoding](#url-encoding)
`SSLError(1, '[SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1081)')`You wrote `https://` for a plain HTTP proxy[Scheme section](#scheme-ssl)
`InvalidURL: Failed to parse: http://user:...` (a traceback, not a retry)A raw special character broke the proxy URL[Password encoding](#url-encoding)

The host in a NewConnectionError is the useful clue. If it names your proxy, pip picked up a proxy setting. If it names pypi.org or files.pythonhosted.org, it didn't.

Then work through these checks in order. Each costs seconds.

  1. What does pip think its config is? python -m pip config debug lists every config file pip looks at, whether it exists, and any PIP_* environment variables it read. A forgotten proxy = line in a global file shows up here.
  2. What proxy variables exist? env | grep -i proxy on Linux or macOS, Get-ChildItem Env: | Where-Object Name -like 'proxy' in PowerShell.
  3. Is the proxy reachable at all? curl -sS -o /dev/null -w '%{http_code}\n' -x http://USER:PASS@gateway.sparkproxy.io:11000 https://pypi.org/simple/six/ should print 200. A timeout here is a network problem that no pip setting can fix; a 407 points at the credentials, and pip isn't to blame.
  4. Run pip with an explicit flag (next section). If that works, your persistent settings are the problem, and the precedence table tells you which one.

One-Off Installs With the --proxy Flag

pip 26.2.2 describes the option as: "Specify a proxy in the form scheme://[user:passwd@]proxy.server:port."

python -m pip install --proxy http://gateway.sparkproxy.io:11000 requests
python -m pip install --proxy http://USER:PASS@gateway.sparkproxy.io:11000 -r requirements.txt

Use python -m pip rather than a bare pip, so the proxy settings apply to the interpreter you think you're installing into.

Three properties of the flag matter, all visible in pip/_internal/cli/index_command.py:

  • It sets the same proxy for both http and https URLs. You can't split them with this flag.
  • Setting it switches the underlying requests session to trust_env = False. From that moment pip ignores HTTP_PROXY, HTTPS_PROXY, NO_PROXY and also REQUESTS_CA_BUNDLE, because requests only reads those when trust_env is on. If a corporate CA bundle used to work through REQUESTS_CA_BUNDLE, pass it with --cert once you add a proxy.
  • Since pip 25.0 the flag also reaches the subprocess that installs build dependencies for source distributions. The changelog for 25.0 (2025-01-26) says "The --proxy command-line option is now respected while installing build dependencies." On older pip, an sdist build could ignore your flag and hang.

Leaving out the scheme still works. We ran --proxy 127.0.0.1:28082 and pip treated it as http://, because requests prepends the scheme. Write it out anyway; the explicit form is what the help text documents.

Free trial

Scraping at scale? Skip the blocks.

Fast, unblockable datacentre proxies with unlimited bandwidth.

Making It Permanent in pip.conf or pip.ini

Typing the flag on every command gets old. Any long option can live in pip's config file under the same name, without the dashes. The file is called pip.conf on Linux and macOS and pip.ini on Windows.

[global]
proxy = http://gateway.sparkproxy.io:11000
timeout = 30

According to the pip configuration docs, there are three levels, loaded in this order, with "each file read overrides any values read from previous files":

LevelLinuxmacOSWindows
Global`/etc/pip.conf` or `/etc/xdg/pip/pip.conf``/Library/Application Support/pip/pip.conf``C:\ProgramData\pip\pip.ini`
User`$HOME/.config/pip/pip.conf` (legacy `$HOME/.pip/pip.conf`)`$HOME/Library/Application Support/pip/pip.conf``%APPDATA%\pip\pip.ini`
Site (per venv)`$VIRTUAL_ENV/pip.conf``$VIRTUAL_ENV/pip.conf``%VIRTUAL_ENV%\pip.ini`
`PIP_CONFIG_FILE`any path, loaded lastany path, loaded lastany path, loaded last

You don't have to remember the paths. Let pip write the file:

python -m pip config set global.proxy http://gateway.sparkproxy.io:11000   # user level
python -m pip config --site set global.proxy http://gateway.sparkproxy.io:11000   # this venv only
python -m pip config list
# global.proxy='http://gateway.sparkproxy.io:11000'

The --site form is the tidy option for a project that needs the proxy while other projects on the same laptop don't. Delete the venv and the setting goes with it.

Watch the section name. A proxy under [install] overrides [global] for pip install only, so pip download and pip index won't see it. Put the proxy in [global] unless you really mean one subcommand.

pip parses these files with Python's RawConfigParser, so a % in a value is taken literally. Percent-encoded passwords (covered below) work in the file exactly as written. We tested http://spark:p%40ss%3Aw%2Frd@127.0.0.1:28083 from a pip.ini and the proxy received the decoded password p@ss:w/rd.

HTTPS_PROXY, PIP_PROXY and the Other Variables

Two unrelated families of variables can steer pip, and mixing them up causes most of the confusion.

pip's own variables. Every option maps to PIP_, so --proxy becomes PIP_PROXY. pip treats it exactly like the flag, including turning off trust_env.

export PIP_PROXY=http://gateway.sparkproxy.io:11000
python -m pip install httpx
$env:PIP_PROXY = "http://gateway.sparkproxy.io:11000"
python -m pip install httpx

The generic proxy variables. HTTPS_PROXY, HTTP_PROXY, ALL_PROXY and NO_PROXY are read by requests (vendored inside pip) through Python's urllib.request.getproxies(), and only when no pip-level proxy is set. Since the index and file hosts are HTTPS, HTTPS_PROXY is the one that counts. We set only HTTP_PROXY and pip went straight to PyPI, because no https entry existed. The shell-side details (lowercase vs uppercase, persisting variables) are the same as for any Python HTTP client; our guide to proxies in Python requests and aiohttp covers how requests picks them up.

There's a fallback almost nobody expects. Reading CPython's urllib/request.py, getproxies() on Windows is getproxies_environment() or getproxies_registry(), and on macOS it falls back to the system network settings. So with no proxy variables at all, pip on Windows can still pick up the proxy configured in Windows Internet Settings. If pip is mysteriously using a proxy you never set, that's where it came from.

This short script prints what the environment layer will hand to pip, plus pip's own config view:

"""Show which proxy settings pip will see, from every source."""
import os
import subprocess
import sys
import urllib.request


def main() -> None:
    print("Python:", sys.executable)
    print("\n[env vars]")
    for name in sorted(os.environ):
        if name.lower().endswith("_proxy") or name.upper().startswith("PIP_"):
            print(f"  {name}={os.environ[name]}")
    print("\n[urllib.request.getproxies()]  (env, then registry or macOS settings)")
    for scheme, url in urllib.request.getproxies().items():
        print(f"  {scheme}: {url}")
    print("\n[pip config debug]")
    out = subprocess.run(
        [sys.executable, "-m", "pip", "config", "debug"],
        capture_output=True, text=True, check=False,
    )
    print(out.stdout or out.stderr)


if __name__ == "__main__":
    main()

Run it with the same interpreter you install with. Anything under getproxies() is used only if PIP_PROXY, the config file and --proxy are all unset.

Who Wins: pip Proxy Precedence, Tested

The documented rule is short: "Command line options override environment variables, which override the values in a configuration file." That rule only covers pip's own settings. The generic variables sit beneath all of them. We ran pip download six==1.17.0 on pip 26.2.2 against two logging proxies, A and B, and recorded which one received the CONNECT pypi.org:443 request.

SetupProxy used
`HTTPS_PROXY=A` onlyA
`HTTPS_PROXY=A` plus `--proxy B`B
`HTTPS_PROXY=A` plus `PIP_PROXY=B`B
`pip.ini proxy = A` plus `HTTPS_PROXY=B`A
`HTTPS_PROXY=A` plus `NO_PROXY=pypi.org,files.pythonhosted.org`none, direct
`--proxy B` plus the same `NO_PROXY`B (NO_PROXY ignored)
`HTTPS_PROXY=A` plus `--proxy ""`none, direct
`HTTPS_PROXY=A` plus `--no-proxy-env`none, direct
`HTTPS_PROXY=A` plus `PIP_NO_PROXY_ENV=1`none, direct
`HTTPS_PROXY=A` plus `PIP_PROXY=` (empty)A

So the effective order is: --proxy flag, then PIP_PROXY, then the config file, and only if all three are empty, HTTPS_PROXY and friends filtered by NO_PROXY.

Two rows deserve attention. NO_PROXY has no effect once a pip-level proxy exists, so you can't use it to send an internal index direct while public packages go through the proxy. Use a separate invocation, or a site-level config in the venv that talks to the internal index. And the last row is a trap: an empty PIP_PROXY does not disable proxying. To bypass inherited HTTPS_PROXY for one run, use --proxy "" or --no-proxy-env, both added in pip 26.2 (2026-07-29). The changelog entry reads "Add a --no-proxy-env (or --proxy "") option to ignore proxies configured via non-pip environment variables". On older pip, an empty --proxy was simply ignored, so unset the variables instead.

One more historical note explains old Stack Overflow answers that contradict this table: before pip 24.1 (changelog 24.1b1, 2024-05-06, "Make the --proxy parameter take precedence over environment variables"), an environment variable could beat the flag.

Passwords With @, :, / or # in the Proxy URL

The general mechanics of user:pass@host credentials and the 407 challenge are in our explainer on how proxy authentication works. What matters for pip is how its vendored requests and urllib3 parse the URL. We sent four raw passwords through --proxy on pip 26.2.2:

Raw password in URLWhat happened
`p@ss`Parsed correctly; urllib3 splits on the last `@`
`p@ss:w/rd`Crash: `InvalidURL: Failed to parse: http://spark:p@ss:w/rd@127.0.0.1:28083`
`ab#cd`Crash: `InvalidURL: Failed to parse`, since `#` starts a URL fragment
`p%40ss%3Aw%2Frd`Worked; the proxy received `p@ss:w/rd`

A single @ surviving by luck is the dangerous case, because it teaches you raw passwords are fine until one contains a /. Percent-encode the username and password every time. Python's standard library does it correctly:

"""Build a pip-safe proxy URL from raw credentials."""
import sys
from urllib.parse import quote


def proxy_url(user: str, password: str, host: str, port: int) -> str:
    return f"http://{quote(user, safe='')}:{quote(password, safe='')}@{host}:{port}"


if __name__ == "__main__":
    print(proxy_url(sys.argv[1], sys.argv[2], "gateway.sparkproxy.io", 11000))
python make_proxy_url.py 'spark-user' 'p@ss:w/rd#1'
# http://spark-user:p%40ss%3Aw%2Frd%231@gateway.sparkproxy.io:11000

The encoded URL works unchanged in --proxy, PIP_PROXY and pip.conf; we checked all three. One Windows catch: inside a .bat or .cmd file, % is the variable marker, so %40 must be written %%40. Interactive PowerShell and Bash need no doubling, but use single quotes in Bash so the shell doesn't touch $ or ! characters.

Credentials in a proxy URL end up in shell history and process listings. For a fixed machine, IP whitelisting avoids the secret entirely (more on that in the CI section).

The SSL Error Caused by an https:// Proxy Scheme

The scheme in the proxy URL describes how pip talks to the proxy, not to PyPI. HTTPS traffic to PyPI is tunnelled through an ordinary HTTP proxy with a CONNECT request, and the TLS session runs end to end inside that tunnel. So a normal forward proxy is written http:// even though every package download is HTTPS.

Write https:// instead and pip opens a TLS handshake with the proxy itself. A plain HTTP proxy answers that with plaintext, and OpenSSL reports the plaintext as a malformed TLS record. We reproduced it against a test proxy that replies HTTP/1.1 400 Bad Request:

WARNING: Retrying (Retry(total=0, connect=None, read=None, redirect=None, status=None)) after connection broken by 'SSLError(1, '[SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1081)')': /simple/six/

Against a proxy that ignores the garbled bytes and waits, the same mistake shows up as a ReadTimeoutError instead, which is harder to spot. The fix is one character group:

# wrong for a normal HTTP proxy
python -m pip install --proxy https://gateway.sparkproxy.io:11000 requests
# right
python -m pip install --proxy http://gateway.sparkproxy.io:11000 requests

Use https:// only when your proxy really accepts TLS on its listening port, which is uncommon. The pip 26.2 changelog includes a fix to "Ensure truststore feature remains active while initially connecting to a HTTPS proxy", so if you do run a TLS proxy, use pip 26.2 or later.

A different SSL failure, CERTIFICATE_VERIFY_FAILED, isn't a scheme problem. It's covered in the last section.

SOCKS5 Proxies and the pip 26.2 TypeError

pip accepts socks5:// and socks5h:// URLs (socks5h resolves DNS on the proxy), but it doesn't ship the SOCKS library. Out of the box you get:

pip._vendor.requests.exceptions.InvalidSchema: Missing dependencies for SOCKS support.

The vendored urllib3 imports a top-level socks module, which comes from PySocks (latest release 1.7.1, uploaded to PyPI on 2019-09-20; we read the date from PyPI's JSON API today). That creates a chicken-and-egg problem on a machine whose only exit is SOCKS. Download the wheel elsewhere and install it offline:

python -m pip download pysocks -d wheels          # on a machine with access
python -m pip install --no-index --find-links wheels pysocks   # on the locked-down box

On pip 26.2.2 that isn't the end of it. With PySocks installed, our SOCKS test failed with:

TypeError: PoolKey.__new__() got an unexpected keyword argument 'key_proxy_ssl_context'. Did you mean 'key_ssl_context'?

The cause is the 26.2 change that passes a proxy_ssl_context to every proxy manager, which urllib3's SOCKS manager doesn't accept. A fix is proposed in pypa/pip pull request #14320, which was still open when we checked it on 2026-10-30. Until it ships, add --use-deprecated=legacy-certs, which makes pip skip the truststore SSL context; in our test the TypeError disappeared and pip went on to make a real SOCKS connection. The cost is that pip then verifies TLS with certifi only, ignoring the system trust store. Pinning pip below 26.2 also avoids it. Our SOCKS5 setup guide covers SOCKS in other Python clients.

For pip specifically, an HTTP proxy is the lower-friction choice. Use SOCKS only when HTTP proxy ports are blocked.

Confirm pip Is Really Using the Proxy

A successful install proves nothing. If the machine also has an open path straight to PyPI, pip may be ignoring your setting and succeeding anyway. Verbose output won't tell you either: we compared pip download -vvv with and without a proxy, and the urllib3 lines (Starting new HTTPS connection (1): pypi.org:443) were identical, because urllib3 logs the target host, not the proxy.

The reliable test is to break the proxy on purpose. If pip honours your setting, a wrong password must produce a 407:

"""Prove pip routes through the proxy by sending a deliberately wrong password."""
import subprocess
import sys

BAD = "http://route-check:wrong-password@gateway.sparkproxy.io:11000"
CMD = [
    sys.executable, "-m", "pip", "download", "six", "--no-deps",
    "--no-cache-dir", "--retries", "1", "--timeout", "10",
    "-d", "pip-route-check", "--proxy", BAD,
]

result = subprocess.run(CMD, capture_output=True, text=True, check=False)
log = result.stdout + result.stderr
if "407 Proxy Authentication Required" in log:
    print("OK: pip reached the proxy (it rejected the bad password)")
elif result.returncode == 0:
    print("WARNING: install succeeded with a bad password; traffic bypassed the proxy")
else:
    print("Inconclusive; last lines:\n" + "\n".join(log.splitlines()[-3:]))

This checks the flag path. To test a persistent setting, drop --proxy from the command and put the bad URL in PIP_PROXY or the config file temporarily. With IP whitelisting there's no password to break, so check the provider dashboard or proxy logs for the request instead.

pip Through a SparkProxy Gateway in CI

If your proxy belongs to your employer, you're done: the sections above cover it. This section is for build runners and VPS hosts that egress through a commercial gateway, shown with SparkProxy (product details checked on sparkproxy.io, 2026-10-30).

Point pip at gateway.sparkproxy.io. Three ports matter: 11000 rotates the exit IP randomly (Random Rotation), 11002 holds one exit IP for 5 minutes (Sticky Sessions), and 13000 speaks SOCKS5. For pip, Random Rotation is fine: every index page and wheel is a separate, stateless HTTPS request, so a changing exit IP breaks nothing. Choose Sticky Sessions on 11002 when a private index or firewall allowlists source addresses and you want one stable IP for the length of an install.

Authentication is either user:pass or IP whitelisting. A fixed build host suits whitelisting: add its public IP in the dashboard, and PIP_PROXY=http://gateway.sparkproxy.io:11000 contains no secret at all. Hosted CI runners change IPs between jobs, so they need user:pass from the secret store:

# .github/workflows/test.yml (excerpt)
env:
  PIP_PROXY: ${{ secrets.SPARKPROXY_PIP_PROXY }}   # already percent-encoded
  PIP_TIMEOUT: "30"
  PIP_RETRIES: "5"
steps:
  - uses: actions/checkout@v4
  - run: python -m pip install -r requirements.txt

PIP_PROXY beats any runner-level HTTPS_PROXY, per the precedence table, so the job uses your gateway even on runners with their own proxy variables. Because it's a pip variable rather than a flag, it also reaches nested pip calls such as build-backend installs. For Docker builds, the proxy must be passed into the build container; our guide to using proxies with Docker covers the build-arg route.

If you assemble the URL in a script, keep the password out of logs:

"""Run pip through the proxy with credentials from the environment, redacted in output."""
import os
import subprocess
import sys
from urllib.parse import quote

user = quote(os.environ["SPARKPROXY_USER"], safe="")
password = quote(os.environ["SPARKPROXY_PASS"], safe="")
proxy = f"http://{user}:{password}@gateway.sparkproxy.io:11000"

env = dict(os.environ, PIP_PROXY=proxy)
cmd = [sys.executable, "-m", "pip", "install", "-r", "requirements.txt"]
print("running:", " ".join(cmd), "via gateway.sparkproxy.io:11000")
proc = subprocess.run(cmd, env=env, capture_output=True, text=True, check=False)
sys.stdout.write(proc.stdout.replace(password, "***"))
sys.stderr.write(proc.stderr.replace(password, "***"))
sys.exit(proc.returncode)

Pricing works by plan and concurrency rather than gigabytes, with unlimited bandwidth included. Plans begin at "Starting from $75/month" (USD, as shown on the SparkProxy site on 2026-10-30), with term discounts of 5% for 3 months, 10% for 6 and 15% for 12. That suits package installs, where a single CI job that pulls large ML wheels would be expensive on a per-GB plan and costs nothing extra here. The 24-hour free trial needs no credit card, enough time to run the route check above against a real gateway.

Failures a Proxy Change Will Not Fix

Stop editing proxy settings if the error is one of these:

Error textReal causeFix
`[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate in certificate chain`A TLS-inspecting corporate proxy re-signs HTTPSTrust the company CA: `--cert /path/ca.pem` or `PIP_CERT`
`error: externally-managed-environment`PEP 668 system Python on Debian, Ubuntu or HomebrewUse a venv; the network is fine
`from versions: none` with no retry warnings above it (at default `--retries`)The package has no release for your Python version or platformCheck `Requires-Python` and available wheels on PyPI
`THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS FILE.`Changed artifact, or a caching proxy serving a stale or altered fileRegenerate hashes, or ask the proxy owner to purge the cache

Two of these touch the proxy without being fixed by a proxy address. TLS inspection is proxy behaviour, but the cure is trusting a certificate. Since pip 24.2 (2024-07-28), pip uses "system certificates and certifi certificates to verify HTTPS connections on Python 3.10+", so a CA installed in the OS trust store often fixes it without any flag. And remember from the flag section that REQUESTS_CA_BUNDLE stops being read as soon as a pip-level proxy is set, so --cert is the dependable route.

If check 3 in the diagnosis list timed out, the machine can't reach the proxy at all. That's a firewall rule or security group, and nothing in pip's configuration can route around it.

Frequently asked questions

FAQ

Pass --proxy http://host:port on the command, set PIP_PROXY, or add proxy = http://host:port under [global] in pip.conf (pip.ini on Windows). python -m pip config set global.proxy http://host:port writes the file for you.

Yes, but only when no pip-level proxy is set. A --proxy flag, PIP_PROXY or a proxy line in the config file overrides HTTPS_PROXY and also makes pip ignore NO_PROXY. HTTP_PROXY alone doesn't cover PyPI, because its index and file hosts are HTTPS.

Percent-encode the username and password, for example @ as %40, : as %3A, / as %2F and # as %23. Raw / or # makes pip crash with InvalidURL: Failed to parse. The encoded URL works in --proxy, PIP_PROXY and pip.conf.

You almost certainly wrote https:// for a plain HTTP proxy. pip then tries a TLS handshake with the proxy, gets plaintext back, and OpenSSL reports [SSL: WRONG_VERSION_NUMBER]. Change the proxy URL to http://; HTTPS downloads still travel encrypted through a CONNECT tunnel.

On pip 26.2 or later, add --no-proxy-env or --proxy "". Setting PIP_PROXY to an empty string doesn't work. On older pip, unset HTTPS_PROXY and HTTP_PROXY for that command, and on Windows check that Internet Settings has no proxy, because Python reads it as a fallback.

Yes, with --proxy socks5h://host:port once PySocks is installed. On pip 26.2.2 SOCKS also needs --use-deprecated=legacy-certs to avoid a TypeError about key_proxy_ssl_context, pending the fix in pip pull request #14320.

Special Discount ยท 20% off

Get 20% off your first month

Premium datacentre proxies with unlimited bandwidth. Use the code at checkout.

Save up to 15% more on quarterly, half-yearly and yearly plans

Claim Discount

About the Author

This guide comes from the SparkProxy Technical Team, the engineers behind SparkProxy's datacenter proxy network (1M+ IPs in 80+ countries over HTTP, HTTPS and SOCKS5) and its Scraping API. We built the precedence table and captured each error string by running pip 26.2.2 against our own logging test proxies, then matched every result to the pip source code and changelog.

Keep reading

Related articles

SSL WRONG_VERSION_NUMBER With a Proxy: The Fix

Getting SSL WRONG_VERSION_NUMBER through a proxy in Python? It's almost always https:// in the proxy URL. The one-character fix, plus every SSLEOFError cause.

SparkProxyยทGuides