pypa/pip · error · URLError

Unexpected HTTP request on what should be a secure…

Error message

Unexpected HTTP request on what should be a secure connection: %s

What it means

Raised by distlib's HTTPSOnlyHandler.http_open. This handler class inherits from both HTTPSHandler and HTTPHandler so that build_opener does not register its own plain-HTTP handler; instead, every plain http:// request is intercepted and rejected. This prevents accidental HTTP traffic (and MITM downgrade attacks) on a connection intended to be HTTPS-only, e.g. when an index page contains an http:// link where an https:// link was expected.

Solutions

  1. Ensure all URLs in your configuration use the https:// scheme.
  2. If the source is an index page, fix the server to serve https:// links.
  3. Verify no redirect in the chain downgrades from HTTPS to HTTP.
  4. If plain HTTP is genuinely required for this endpoint, use HTTPSHandler instead of HTTPSOnlyHandler (accepting the security trade-off).

Example fix

# before — plain HTTP URL rejected
opener = build_opener(HTTPSOnlyHandler(...))
opener.open('http://pypi.org/simple/')  # raises

# after — use HTTPS
opener.open('https://pypi.org/simple/')
Defensive patterns

Strategy: validation

Validate before calling

from urllib.parse import urlsplit

def ensure_https(url):
    parts = urlsplit(url)
    if parts.scheme != 'https':
        raise ValueError(f'Expected https:// URL, got {parts.scheme}:// for {url}')
    return url

Try / catch

from urllib.error import URLError
try:
    opener.open(url)
except URLError as e:
    if 'Unexpected HTTP request' in str(e):
        log.error('HTTP URL used on HTTPS-only handler: %s', url)
    raise

Prevention

When it happens

Trigger: Building a URL opener with HTTPSOnlyHandler and then requesting a URL with an http:// scheme, or following a redirect/link from an HTTPS index page that points to a plain HTTP URL.

Common situations: A PyPI index serves HTML with http:// links by mistake; a redirect chain degrades from https to http; developer accidentally passes an http:// URL where https:// was intended; corporate proxy strips TLS and serves plain HTTP.

Related errors


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

Appendix: 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 f399c37189)