{"id":"4233027a71399f37","repo":"pypa/pip","slug":"unexpected-http-request-on-what-should-be-a-secure","errorCode":null,"errorMessage":"Unexpected HTTP request on what should be a secure connection: %s","messagePattern":"Unexpected HTTP request on what should be a secure connection: (.+?)","errorType":"http","errorClass":"URLError","httpStatus":null,"severity":"error","filePath":"src/pip/_vendor/distlib/util.py","lineNumber":1559,"sourceCode":"                if 'certificate verify failed' in str(e.reason):\n                    raise CertificateError('Unable to verify server certificate '\n                                           'for %s' % req.host)\n                else:\n                    raise\n\n    #\n    # To prevent against mixing HTTP traffic with HTTPS (examples: A Man-In-The-\n    # Middle proxy using HTTP listens on port 443, or an index mistakenly serves\n    # HTML containing a http://xyz link when it should be https://xyz),\n    # you can use the following handler class, which does not allow HTTP traffic.\n    #\n    # It works by inheriting from HTTPHandler - so build_opener won't add a\n    # handler for HTTP itself.\n    #\n    class HTTPSOnlyHandler(HTTPSHandler, HTTPHandler):\n\n        def http_open(self, req):\n            raise URLError('Unexpected HTTP request on what should be a secure '\n                           'connection: %s' % req)\n\n\n#\n# XML-RPC with timeouts\n#\nclass Transport(xmlrpclib.Transport):\n\n    def __init__(self, timeout, use_datetime=0):\n        self.timeout = timeout\n        xmlrpclib.Transport.__init__(self, use_datetime)\n\n    def make_connection(self, host):\n        h, eh, x509 = self.get_host_info(host)\n        if not self._connection or host != self._connection[0]:\n            self._extra_headers = eh\n            self._connection = host, httplib.HTTPConnection(h)\n        return self._connection[1]","sourceCodeStart":1541,"sourceCodeEnd":1577,"githubUrl":"https://github.com/pypa/pip/blob/d7d0d0a39494e28ec1c407bd0680e4a4d1067791/src/pip/_vendor/distlib/util.py#L1541-L1577","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before\nopener = build_opener(HTTPSOnlyHandler())\nopener.open('http://pypi.example.com/simple/')  # raises\n\n# after\nopener = build_opener(HTTPSOnlyHandler())\nopener.open('https://pypi.example.com/simple/')","handlingStrategy":"try-catch","validationCode":"from urllib.parse import urlparse\ndef assert_https(url):\n    if urlparse(url).scheme != 'https':\n        raise ValueError(f'refusing non-https URL: {url}')\n    return url","typeGuard":"from urllib.parse import urlparse\ndef is_https_url(url: str) -> bool:\n    return urlparse(url).scheme == 'https'","tryCatchPattern":"from urllib.error import URLError\ntry:\n    opener.open(url)\nexcept URLError as e:\n    if 'Unexpected HTTP request' in str(e):\n        # scheme downgrade detected; log and fall back to a known-good https URL\n        ...\n    raise","preventionTips":["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."],"tags":["network","security","ssl","https","distlib","pip"],"analyzedSha":"d7d0d0a39494e28ec1c407bd0680e4a4d1067791","analyzedAt":"2026-08-04T20:55:04.259Z","schemaVersion":2}