Two different faults print the same pip traceback, and the repair that suits one makes the other worse. A client too old to name a tunnelled host needs an upgrade. A proxy that re-signs traffic needs a trusted root certificate, not a downgrade. Work out which of the two you have before you edit a proxy variable, because the paths lead in opposite directions.

Read the Last Frames Before Touching Settings

Where the traceback ends tells you which layer gave up. If the closing frames sit inside pip's vendored urllib3 TLS code and the text complains about a hostname, the client library could not finish a verification step, and that is a version problem far more often than a certificate problem. If the text instead reports a refused connection, an unresolved proxy name or an HTTP 407, the tunnel endpoint is at fault and no upgrade will change it.

Capture the pip version and the proxy variables in the same shell that fails. A shell opened before the tunnel came up carries different values from one opened after it, and the diff between them is often the whole diagnosis.

python3 -m pip --version
env | grep -i -E 'proxy|no_proxy'

When the Client Is Older Than the Tunnel

Older pip releases bundled an HTTPS stack that lost the destination hostname once the socket was wrapped by a tunnel. The check_hostname message is what that stack emits when it reaches verification without a name to compare against. An upgrade removes the fault because the newer client keeps the name across the handshake, and the change is cheap enough to try first whenever the closing frames point at vendored TLS code.

If the index cannot be reached from the failing machine, fetch a current wheel on a host that can and install from the file. Use the filename you actually downloaded rather than a version copied from a blog post.

python3 -m pip install --upgrade pip
python3 -m pip install ./pip-24.2-py3-none-any.whl

When the Proxy Re-Signs Traffic

A proxy that terminates TLS and re-issues certificates under a corporate root produces a different error: certificate verify failed, not a hostname complaint. That difference is the clue, because the client reached a server and then rejected the identity it was shown. The repair is to trust that root explicitly, either for one command or through the PIP_CERT variable, so the client can build a chain to what the proxy presents. Adding the root to the system trust store also works, but it changes trust for every program on the machine, which is a larger decision than fixing one package install.

Keep --trusted-host as a diagnostic, never as a resting state. It switches off the check you were trying to keep, so a genuinely wrong certificate looks healthy afterwards. If you used it to get one package installed, remove it and repair the chain before the next release.

Your own Python code ignores pip's configuration entirely. A requests call needs the proxy and the bundle passed where that session can see them, which is why the block below sets both on the session object.

pip install --cert /usr/local/share/ca-certificates/corp-root.crt requests
export PIP_CERT=/usr/local/share/ca-certificates/corp-root.crt
s = requests.Session()
s.proxies = {"https": "http://10.20.30.40:8080"}
s.verify = "/usr/local/share/ca-certificates/corp-root.crt"
s.get("https://pypi.org/simple/", timeout=15)

When the Endpoint or Port Is Wrong

A tunnel client often rewrites the system proxy as it connects, which can leave pip pointed at a host that never answers on that port. Test the endpoint with a client that is not pip before you change any pip setting. If curl fails with the same proxy argument, the fault sits above pip and no amount of configuration inside pip will help.

The command flag and the environment variables say the same thing to different scopes: the flag applies to one invocation, the variables to every process started afterwards. Add NO_PROXY for an internal index so private package traffic skips the detour through the tunnel.

curl -x http://proxy.corp.internal:8080 --head https://pypi.org/simple/
pip install --proxy http://proxy.corp.internal:8080 requests

Persist the Setting and Prove the Path Works

Store the proxy in pip's own configuration when it should outlive the current shell, then read back what pip will really use. The debug output names the file and the variable behind each value, which settles arguments about precedence faster than reading the documentation does.

Verification deliberately uses two clients. The standard-library request proves the route and the trust chain, while the download proves that pip's own configuration agrees with them. When the first succeeds and the second fails, the difference is inside pip rather than in the network.

pip config set global.proxy http://proxy.corp.internal:8080
pip config debug
python3 -c "import urllib.request as r; print(r.urlopen('https://pypi.org/simple/').status)"
pip download --no-deps -d /tmp/wheelcheck requests
What you seeFirst commandRepair
hostname message inside vendored TLS framespython3 -m pip --versionupgrade the client
certificate verify failed after the tunnelcurl -x http://proxy.corp.internal:8080 --head https://pypi.org/simple/trust the corporate root
connection refused or HTTP 407env | grep -i -E 'proxy|no_proxy'correct host, port or credentials

Mikanda mikatakata