pypa/pip · error · URLError
Unexpected HTTP request on what should be a secure connectio
Error message
Unexpected HTTP request on what should be a secure connection: %s
What it means
Raised by distlib's HTTPSOnlyHandler.http_open when a plaintext HTTP request is dispatched through an opener that was configured to forbid insecure traffic. The handler inherits from both HTTPSHandler and HTTPHandler so that build_opener will not register a separate HTTP handler, intercepting any http:// URL and converting it into a URLError. It exists to defend against MITM proxies and misconfigured indexes that redirect or link to non-HTTPS URLs on port 443.
Source
Thrown at src/pip/_vendor/distlib/util.py:1559
if 'certificate verify failed' in str(e.reason):
raise CertificateError('Unable to verify server certificate '
'for %s' % req.host)
else:
raise
#
# To prevent against mixing HTTP traffic with HTTPS (examples: A Man-In-The-
# Middle proxy using HTTP listens on port 443, or an index mistakenly serves
# HTML containing a http://xyz link when it should be https://xyz),
# you can use the following handler class, which does not allow HTTP traffic.
#
# It works by inheriting from HTTPHandler - so build_opener won't add a
# handler for HTTP itself.
#
class HTTPSOnlyHandler(HTTPSHandler, HTTPHandler):
def http_open(self, req):
raise URLError('Unexpected HTTP request on what should be a secure '
'connection: %s' % req)
#
# XML-RPC with timeouts
#
class Transport(xmlrpclib.Transport):
def __init__(self, timeout, use_datetime=0):
self.timeout = timeout
xmlrpclib.Transport.__init__(self, use_datetime)
def make_connection(self, host):
h, eh, x509 = self.get_host_info(host)
if not self._connection or host != self._connection[0]:
self._extra_headers = eh
self._connection = host, httplib.HTTPConnection(h)
return self._connection[1]View on GitHub (pinned to d7d0d0a394)
Solutions
- Ensure the index-url and find-links entries in pip.conf / the opener config all use https:// URLs.
- Fix the server so any http:// request redirects to its https:// equivalent (301 to the same path on https).
- Remove the MITM/proxy that is downgrading the scheme, or configure it to tunnel CONNECT for https.
- If HTTP must be allowed intentionally, do not register HTTPSOnlyHandler; use the plain HTTPSHandler instead.
Example fix
# before
opener = build_opener(HTTPSOnlyHandler())
opener.open('http://pypi.example.com/simple/') # raises
# after
opener = build_opener(HTTPSOnlyHandler())
opener.open('https://pypi.example.com/simple/') Defensive patterns
Strategy: try-catch
Validate before calling
from urllib.parse import urlparse
def assert_https(url):
if urlparse(url).scheme != 'https':
raise ValueError(f'refusing non-https URL: {url}')
return url Type guard
from urllib.parse import urlparse
def is_https_url(url: str) -> bool:
return urlparse(url).scheme == 'https' Try / catch
from urllib.error import URLError
try:
opener.open(url)
except URLError as e:
if 'Unexpected HTTP request' in str(e):
# scheme downgrade detected; log and fall back to a known-good https URL
...
raise Prevention
- Pin index-url / find-links to https:// in pip.conf and in any custom opener.
- Configure the server to redirect http -> https rather than serving http directly.
- Reserve HTTPSOnlyHandler for contexts where plaintext must be impossible; do not use it if you legitimately need http:// URLs.
When it happens
Trigger: Constructing a PackageIndex/https-only opener with HTTPSOnlyHandler and then issuing a request whose resolved URL scheme is http (e.g. a redirect from https://index to http://mirror, or an HTML page embedding an http:// link). Also triggered when a pip-style download attempts to follow a Location header that downgrades the scheme to http.
Common situations: Custom package indexes or dev mirrors still served over HTTP; corporate transparent proxies that rewrite https to http; a typo'd index-url in pip.conf pointing at http://; CDN misconfiguration returning an http redirect for an asset.
Related errors
- Unable to verify server certificate for %s
- No matching distribution found for {query}
- XMLRPC request failed [code: {fault.faultCode}] {fault.fault
- {path} is a symlink; Will not return uid for symlinks
- Unknown hash name: {hash_name}
AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04).
Data as JSON: /data/errors/4233027a71399f37.json.
Report an issue: GitHub.