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

  1. Ensure the index-url and find-links entries in pip.conf / the opener config all use https:// URLs.
  2. Fix the server so any http:// request redirects to its https:// equivalent (301 to the same path on https).
  3. Remove the MITM/proxy that is downgrading the scheme, or configure it to tunnel CONNECT for https.
  4. 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

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


AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04). Data as JSON: /data/errors/4233027a71399f37.json. Report an issue: GitHub.