pypa/pip · error · SSLVerificationError
ssl-verification-failed
ssl-verification-failed
Error message
Failed to establish a secure connection to {host} while fetching {url} What it means
Raised as SSLVerificationError (the bare, non-MaxRetryError path) when a urllib3.exceptions.SSLError occurs during a non-streamed response, after the TLS handshake. It means certificate verification against the host failed and TLS could not be established.
Solutions
- Update CA roots: pip install --upgrade certifi, or update OS ca-certificates.
- Point pip at the correct CA bundle with --cert corporate-ca.pem.
- Configure the corporate proxy's CA properly; fix system time if skewed.
- Only as a last resort, use --trusted-host to bypass verification for that host (insecure).
Example fix
# before pip install pkg # MITM proxy with untrusted CA # after pip install --cert /etc/ssl/corporate-ca.pem pkg
Defensive patterns
Strategy: validation
Validate before calling
import ssl, certifi
def trust_store_ok(host: str) -> bool:
ctx = ssl.create_default_context(cafile=certifi.where())
try:
with ctx.wrap_socket(socket.create_connection((host, 443)), server_hostname=host):
return True
except ssl.SSLError:
return False Try / catch
from pip._internal.exceptions import SSLVerificationError
try:
run_pip_install(["-i", url, "pkg"])
except SSLVerificationError:
run_pip_install(["--cert", "/etc/ssl/private-ca.pem", "-i", url, "pkg"]) Prevention
- Keep certifi and OS ca-certificates current.
- Register corporate/internal CAs in the system trust store.
- Use --cert for non-standard CAs; avoid --trusted-host unless required.
When it happens
Trigger: Server presents a self-signed, expired, or untrusted certificate; CA bundle missing or out of date; MITM proxy injecting its own cert; system clock skewed so notBefore/notAfter checks fail; TLS version/cipher mismatch.
Common situations: Corporate TLS-intercepting proxies; stale certifi/ca-certificates; air-gapped environments with private CAs; container images with old CA bundles.
Related errors
AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08).
Data as JSON: /api/errors/596ad64fddd2664e.
Report an issue: GitHub.
Appendix: source
Thrown at src/pip/_internal/network/utils.py:180
Note: requests.ConnectionError is the parent class of
requests.ProxyError, requests.SSLError, and requests.ConnectTimeout
so these errors are also handled here.
"""
url = redact_auth_from_url(url)
raw_hostname = urlsplit(url).hostname or urlsplit(url).netloc
reason = error.args[0] if error.args else error
# NewConnectionError is a subclass of TimeoutError for some reason...
if isinstance(reason, urllib3.exceptions.TimeoutError) and not isinstance(
reason, urllib3.exceptions.NewConnectionError
):
# A bare timeout error can occur during non-streamed responses. Don't
# ask me how.
_raise_timeout_error(reason, url, raw_hostname, timeout)
if isinstance(reason, urllib3.exceptions.SSLError):
# A bare SSL error can occur during non-streamed responses, after the
# initial connection and TLS handshake have completed.
raise SSLVerificationError(url, raw_hostname, reason)
# At this point, all errors should be wrapped in MaxRetryError.
if not isinstance(reason, urllib3.exceptions.MaxRetryError):
raise ConnectionFailedError(url, raw_hostname, reason)
max_retry_error = reason
assert isinstance(max_retry_error.pool, urllib3.connectionpool.HTTPConnectionPool)
host = max_retry_error.pool.host
proxy = max_retry_error.pool.proxy
# Narrow the reason further to the specific error from the last retry.
reason = max_retry_error.reason
if isinstance(reason, urllib3.exceptions.SSLError):
raise SSLVerificationError(url, host, reason)
if isinstance(reason, urllib3.exceptions.TimeoutError) and not isinstance(
reason, urllib3.exceptions.NewConnectionError
):
_raise_timeout_error(reason, url, host, timeout)View on GitHub (pinned to f399c37189)