Introduction

When pip connects to a private PyPI registry (such as Artifactory, Nexus, or Devpi) over HTTPS, TLS certificate verification errors occur if the registry uses an internal certificate authority not in pip's trust store. This error blocks all package installations from the private registry, breaking CI/CD pipelines, Docker builds, and developer onboarding. The error is often confusing because pip's error message references SSLError rather than clearly indicating a certificate trust issue, and the fix depends on whether you should properly configure certificate verification or bypass it entirely.

Symptoms

``` 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:1006)'))': /simple/my-private-package/

ERROR: Could not find a version that satisfies the requirement my-private-package==1.2.3 (from versions: none) ERROR: No matching distribution found for my-private-package==1.2.3 ```

Or when using --index-url:

bash
ERROR: Could not install packages due to an OSError: HTTPSConnectionPool(host='pypi.internal.company.com', port=443):
Max retries exceeded with url: /simple/my-package/ (Caused by SSLError(SSLCertVerificationError(1,
'[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate in certificate chain')))

Common Causes

  • Self-signed certificate on private registry: The registry uses a self-signed or internally-signed certificate
  • Missing CA bundle configuration: pip does not know about the internal CA that signed the registry certificate
  • Certificate expired or not yet valid: The registry certificate has expired or the server clock is wrong
  • SNI not supported by older Python: Python versions before 3.7 may not support Server Name Indication properly
  • Mixed index URLs: requirements.txt has both public PyPI and private registry URLs, causing confusion
  • Corporate MITM proxy: A corporate proxy intercepts TLS traffic with its own certificate

Step-by-Step Fix

Step 1: Configure the CA bundle (preferred secure approach)

bash
# Point pip to the internal CA certificate
pip install my-private-package \
    --index-url https://pypi.internal.company.com/simple/ \
    --cert /etc/ssl/certs/company-internal-ca.pem

Make it permanent by adding to pip config:

bash
pip config set global.cert /etc/ssl/certs/company-internal-ca.pem
pip config set global.index-url https://pypi.internal.company.com/simple/

Or in ~/.config/pip/pip.conf:

ini
[global]
index-url = https://pypi.internal.company.com/simple/
cert = /etc/ssl/certs/company-internal-ca.pem
extra-index-url = https://pypi.org/simple

Step 2: Use trusted-host for development environments

If you cannot obtain the CA certificate (e.g., in a development environment), use trusted-host to skip TLS verification for the specific host:

bash
pip install my-private-package \
    --index-url https://pypi.internal.company.com/simple/ \
    --trusted-host pypi.internal.company.com

In requirements.txt:

bash
--index-url https://pypi.internal.company.com/simple/
--trusted-host pypi.internal.company.com
my-private-package==1.2.3
requests>=2.28.0

Step 3: Handle corporate MITM proxies

If your corporate proxy intercepts TLS:

```bash # Set the proxy CA bundle export REQUESTS_CA_BUNDLE=/etc/ssl/certs/corporate-proxy-ca.pem export SSL_CERT_FILE=/etc/ssl/certs/corporate-proxy-ca.pem

pip install my-private-package --index-url https://pypi.internal.company.com/simple/ ```

In Docker builds:

```dockerfile FROM python:3.11-slim

# Copy corporate CA certificate COPY corporate-ca.pem /usr/local/share/ca-certificates/ RUN update-ca-certificates

ENV REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt ENV PIP_CERT=/etc/ssl/certs/ca-certificates.crt

RUN pip install --no-cache-dir my-private-package \ --index-url https://pypi.internal.company.com/simple/ ```

Prevention

  • Use pip config list to verify the effective configuration before troubleshooting
  • Pin the cert path in CI/CD pipeline configuration, not in developer machines
  • Add a health check in Docker builds: RUN pip install --dry-run my-private-package to catch TLS issues early
  • Use pip download to pre-download packages and cache them, reducing registry dependency
  • Set PIP_DISABLE_PIP_VERSION_CHECK=1 in CI to avoid unnecessary registry calls
  • Monitor private registry certificate expiration dates and rotate CA bundles before expiration
  • Document the private registry TLS setup in your team's onboarding wiki