Introduction

pip uses TLS to securely download packages from PyPI, but SSL certificate verification fails when the system's CA certificate bundle is outdated, when behind a corporate proxy that performs TLS interception, or when the certifi package is not installed. The error SSL: CERTIFICATE_VERIFY_FAILED prevents any package installation from PyPI, effectively breaking the development environment. While the --trusted-host flag bypasses verification, it exposes the system to man-in-the-middle attacks and should only be used as a last resort with full understanding of the risks.

Symptoms

bash
WARNING: Retrying (Retry(total=4, connect=None, read=None, redirect=None, status=None)) after connection broken by 'SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1007)'))': /simple/requests/

Or:

bash
Could not fetch URL https://pypi.org/simple/requests/: There was a problem confirming the ssl certificate: HTTPSConnectionPool(host='pypi.org', port=443): Max retries exceeded with url: /simple/requests/ (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED]')))

Common Causes

  • Outdated CA certificates: System CA bundle does not include recent root certificates
  • Corporate proxy with TLS inspection: Proxy replaces certificates with corporate CA
  • certifi package missing: Python's certifi CA bundle not installed
  • Python compiled without SSL support: Custom Python build missing OpenSSL
  • System clock incorrect: Certificate appears expired due to wrong system time
  • Network firewall blocking: Firewall intercepts HTTPS traffic

Step-by-Step Fix

Step 1: Update certifi and verify certificates

```bash # Install/update certifi pip install --upgrade certifi

# Find certifi's CA bundle path python -c "import certifi; print(certifi.where())" # Output: /usr/local/lib/python3.11/site-packages/certifi/cacert.pem

# Verify the certificate chain python -c " import ssl import socket

context = ssl.create_default_context() with socket.create_connection(('pypi.org', 443)) as sock: with context.wrap_socket(sock, server_hostname='pypi.org') as ssock: print('SSL verified:', ssock.version()) print('Certificate:', ssock.getpeercert()) " ```

Step 2: Configure corporate proxy with custom CA

```bash # Set the CA bundle for pip export PIP_CERT=/path/to/corporate-ca-bundle.crt export REQUESTS_CA_BUNDLE=/path/to/corporate-ca-bundle.crt

# Or configure in pip.conf # ~/.pip/pip.conf (Linux) or %APPDATA%\pip\pip.ini (Windows) [global] cert = /path/to/corporate-ca-bundle.crt proxy = http://proxy.company.com:8080

# Install packages pip install requests ```

Step 3: Use trusted-host as last resort

```bash # ONLY use when no other option is available # This disables SSL verification and is vulnerable to MITM attacks pip install --trusted-host pypi.org --trusted-host files.pythonhosted.org requests

# Safer: Use internal PyPI mirror with proper certificates pip install --index-url https://internal-pypi.company.com/simple/ \ --cert /path/to/corporate-ca-bundle.crt \ requests ```

Prevention

  • Keep certifi package updated with pip install --upgrade certifi
  • Configure PIP_CERT environment variable in shell profile for corporate environments
  • Use a private PyPI mirror (devpi, Artifactory) with proper certificate management
  • Add certificate verification tests to CI/CD pipelines
  • Document corporate proxy configuration in team onboarding guides
  • Never use --trusted-host in production deployment scripts
  • Use pip.conf/requirements.txt with explicit certificate paths for reproducible builds