pypa/pip · error · CertificateError
Unable to verify server certificate for
Error message
Unable to verify server certificate for %s
What it means
Raised by distlib's HTTPSHandler.https_open when an HTTPS request fails with a 'certificate verify failed' URLError. The handler re-wraps the underlying ssl error into a CertificateError naming the offending host, so the caller knows exactly which server's certificate chain could not be validated against the configured CA bundle (ca_certs). This guards against MITM and stale/expired server certificates during pip's package downloads.
Solutions
- Install or update the system CA bundle (e.g. apt-get install ca-certificates, update-ca-certificates) so the server's root CA is trusted.
- If the server uses a private/internal CA, point ca_certs to a PEM file containing that root certificate.
- Verify the server's certificate is not expired and that the hostname matches the SAN field using openssl s_client -connect host:443.
- If this is a known-internal mirror, configure pip to trust it via --trusted-host after confirming it is safe, understanding this disables verification.
- Renew or re-issue the server certificate if it is expired or has an incomplete chain.
Example fix
// before — no CA bundle, verification fails handler = HTTPSHandler(ca_certs=None) // after — supply the correct CA bundle handler = HTTPSHandler(ca_certs='/etc/ssl/certs/ca-certificates.crt', check_domain=True)
Defensive patterns
Strategy: validation
Validate before calling
import ssl, socket
def verify_cert_chain(host, port=443, ca_certs=None):
ctx = ssl.create_default_context(cafile=ca_certs)
with socket.create_connection((host, port)) as sock:
with ctx.wrap_socket(sock, server_hostname=host) as ssock:
cert = ssock.getpeercert()
return cert is not None Try / catch
from ssl import CertificateError
try:
opener.open('https://example.com/')
except CertificateError as e:
log.error('Cert verification failed for %s — check CA bundle', e)
raise Prevention
- Keep the system CA bundle updated (ca-certificates package).
- Pre-validate custom CA files exist and are non-empty before passing to HTTPSHandler.
- In CI, install corporate root CAs into the trust store.
- Monitor certificate expiry on internal mirrors.
When it happens
Trigger: Calling urlopen/https_open against a host whose TLS certificate is expired, self-signed, signed by an untrusted CA, has a hostname mismatch, or whose chain is incomplete — while ca_certs is configured on the HTTPSHandler. Also triggered when the system CA bundle is empty or missing.
Common situations: Corporate proxy with its own root CA not installed in the trust store; air-gapped environment with no CA bundle; pip pointed at an internal index with a self-signed cert; expired Let's Encrypt cert on a private PyPI mirror; container images that ship without ca-certificates.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- ssl-missing
- ssl-verification-failed
- Unexpected HTTP request on what should be a secure…
- Absolute paths are not supported in pylock files obtained…
- Can not open an editor for a file name containing
AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08).
Data as JSON: /api/errors/74cf838e29effcb7.
Report an issue: GitHub.
Appendix: source
Thrown at src/pip/_vendor/distlib/util.py:1542
pass a connection class to do_open, but it doesn't actually check for
a class, and just expects a callable. As long as we behave just as a
constructor would have, we should be OK. If it ever changes so that
we *must* pass a class, we'll create an UnsafeHTTPSConnection class
which just sets check_domain to False in the class definition, and
choose which one to pass to do_open.
"""
result = HTTPSConnection(*args, **kwargs)
if self.ca_certs:
result.ca_certs = self.ca_certs
result.check_domain = self.check_domain
return result
def https_open(self, req):
try:
return self.do_open(self._conn_maker, req)
except URLError as e:
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)View on GitHub (pinned to f399c37189)