Quick fix
# Option A (emergency, get installed now): skip cert verification for one host
pip install --trusted-host <mirror-host> --index-url <mirror-url> <package>
# Persist: pip config set global.trusted-host <mirror-host>
# Option B (proper fix, recommended): import the CA into the system trust store, point pip at the system bundle
sudo cp your-ca.crt /usr/local/share/ca-certificates/pipssl.crt
sudo update-ca-certificates
pip config set global.cert /etc/ssl/certs/ca-certificates.crt
# then a normal pip install works, no --trusted-host needed
What you will run into
pip install suddenly fails with a long error:
ERROR: Exception:
HTTPSConnectionPool(host='pypi.org', port=443): Max retries exceeded with url: /simple/xxx/
SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate (_ssl.c:1081)'))
The confusing part: the browser opens PyPI fine, curl downloads fine, only pip reports a certificate error; or you switch to a domestic mirror (Tsinghua/Aliyun) and get the exact same error.
Common scenes: an internal PyPI mirror (Nexus/devpi), a corporate/campus SSL-interception proxy, a private repo with a self-signed cert, or an expired certificate.
Before you reflexively add --trusted-host, know that it skips verification — it takes the lock off HTTPS, and packages installed that way are not guaranteed to be what they claim to be.
Why this happens
When pip downloads over HTTPS, Python's ssl module runs certificate chain validation: the server certificate must be issued by a CA that pip trusts, every certificate in the chain must be present, unexpired, and the hostname must match.
When validation fails, pip fails rather than download — that is a security design, not a bug.
"A broken trust chain" usually means one of three things:
- Self-signed certificate: the server uses a certificate it issued to itself (common for internal mirrors and private repos) — it is not in any CA trust store →
self-signed certificate/unable to get local issuer certificate; - Corporate proxy MITM: the network uses an SSL-interception proxy that issues its own certificate for every site. The browser works because the proxy CA was installed there; pip uses its own separate trust store, so it fails — switching mirrors does not help, because the certificate is issued by the proxy, not by the mirror;
- Expired certificate / wrong system clock: you see
certificate has expired.
What --trusted-host does is tell pip to skip verification for that host — it is not a fix, it is a bypass. Verified locally (see evidence): with --trusted-host the SSL stage passes straight through.
Fine as an emergency, but as a habit it means nobody verifies "is this really pypi.org".
Step by step
1. Confirm the error shape — Required
pip install --index-url https://<your-source>/simple/ some-package 2>&1 | tail -5
# Seeing CERTIFICATE_VERIFY_FAILED / self-signed / unable to get local issuer = this page
Then inspect the server certificate to tell "self-signed" from "private CA":
echo | openssl s_client -connect <mirror-host>:443 -showcerts 2>/dev/null | grep -E "subject=|issuer=" | head -4
# subject=CN=xxx equals issuer=CN=xxx = self-signed; issuer is an internal CA = MITM/private CA
2. Figure out which scenario — Required
- Internal mirror / private repo → ask the admin for the CA (Option B);
- Corporate/campus proxy interception → ask IT for the proxy CA; switching mirrors is useless (the cert is issued by the proxy, every host fails the same way);
certificate has expired→ check the clock first (date), then think about expiry;- Browser fine, pip fails → the classic "pip trust store missing the CA" signature; Option B is the direct cure.
3. Proper fix: import the CA into the system trust store — Required
sudo cp ca.crt /usr/local/share/ca-certificates/pipssl.crt
sudo update-ca-certificates # "1 added" in the output = success
4. Point pip at the system trust store — Required
pip config set global.cert /etc/ssl/certs/ca-certificates.crt
# or env var: export PIP_CERT=/etc/ssl/certs/ca-certificates.crt
# or one-shot: pip install --cert /etc/ssl/certs/ca-certificates.crt ...
5. The bypass is for emergencies only — Optional
pip install --trusted-host <mirror-host> --index-url <mirror-url> <package>
Only for "internal test env, need it now". Once installed, switch to Option B. And remember: packages installed while verification is off are not guaranteed to come from a trustworthy source.
How to confirm it works
# 1. Install without any --trusted-host / --cert (public source or yours)
pip install --index-url https://<your-source>/simple/ <package>
# 2. Positively verify the chain is trusted
openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt your-cert.crt
# "OK" = the certificate is now accepted by the system trust store
Pass criteria: no bypass flag, no SSL error, install succeeds — all three at once.
Other environments
| Environment | Behavior & fix |
|---|---|
| Python 3.14 + IP-literal source (https://127.0.0.1:port) | ValueError: check_hostname requires server_hostname (verified 2026-09-01): 3.14 sends no SNI for IP literals, ssl hard-fails — use a hostname (localhost or internal DNS), not an IP |
| Windows | Import CA: certutil -addstore Root ca.crt (Trusted Root Certification Authorities); point pip: pip config set global.cert C:\path\ca-bundle.crt |
| macOS | Import into Keychain (System) and trust it; pip: same --cert pointing at the exported bundle |
| Docker container | Containers don't inherit host CAs: COPY ca.crt /usr/local/share/ca-certificates/ && update-ca-certificates at build time; pip config at runtime too |
| Switching mirrors still fails | Likely proxy MITM: the cert is issued by the proxy, changing hosts does nothing — install the proxy CA (step 2) |
| Wrong system clock | certificate has expired: fix the time first (timedatectl / ntp), then deal with the cert |
Symptom
pip install reports SSLError(SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate) or unable to get local issuer certificate / certificate has expired; the browser opens the package source and curl works, only pip fails; the error stays identical after switching mirrors.
Cause
pip's HTTPS certificate chain validation fails: the server certificate is not issued by any CA in pip's trust store (certifi). Three origins: self-signed (internal mirror / private repo), a corporate SSL-interception proxy's private CA (mirror switching is useless), or an expired certificate / wrong clock. --trusted-host skips verification; it does not fix anything.
Solution
- Proper fix: import the CA into the system trust store (
/usr/local/share/ca-certificates/+update-ca-certificates), then point pip at the system bundle (pip config set global.cert /etc/ssl/certs/ca-certificates.crt) — verified: SSL stage passes. - Emergency bypass:
pip install --trusted-host <host> ...— rescue only; as a habit it disables HTTPS identity verification. - Scenario routing: internal mirror → ask admin for CA; corporate proxy → ask IT for the interception cert (mirror switching useless);
expired→ fix the clock first. - Python 3.14 note: use a hostname, not an IP literal, for the source URL (verified: triggers
check_hostname requires server_hostname).
Verification evidence
- 2026-09-01 local test (WSL Ubuntu 26.04, Python 3.14.4, pip 25.1.1; self-hosted HTTPS source at 127.0.0.1:18443 with a self-signed CA, SAN=DNS:localhost,IP:127.0.0.1):
local repro log 2026-09-01 - Baseline:
pip install --index-url https://localhost:18443/simple/ six→[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate (_ssl.c:1081)(reproduced); - Bypass:
--trusted-host localhost→ SSL stage passes, thenNo matching distribution(verified: skip works); - Proper fix: CA copied to
/usr/local/share/ca-certificates/+update-ca-certificates+pip --cert /etc/ssl/certs/ca-certificates.crt→ SSL stage passes (verified); - Negative control: CA removed, no
--cert→ back toCERTIFICATE_VERIFY_FAILED: self-signed certificate(verified: the fix really depends on the trust store, not a cache); - Variant:
https://127.0.0.1:18443(IP literal) →ValueError: check_hostname requires server_hostname(Python 3.14 new behavior, verified).
Related canonicals
- pip install slow/timeout (China mirror switch) — switch mirrors first, then certificates; cert errors after switching = this page
- Proxy env vars set but not taking effect (case trap) — proxy works but cert errors persist = MITM cert not installed
- JDK legacy TLS upgrade — same TLS-layer family (protocol intersection / cert chain), Java side
Applicable tools (1)
This problem is linked from the tool pages below — reachable from either side.
pipSource & evidence
← Coding Agents & Terminal domain | Problem Catalog Experience | Facing another issue? Go to the Helpdesk