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:
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.txthas 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)
# 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.pemMake it permanent by adding to pip config:
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:
[global]
index-url = https://pypi.internal.company.com/simple/
cert = /etc/ssl/certs/company-internal-ca.pem
extra-index-url = https://pypi.org/simpleStep 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:
pip install my-private-package \
--index-url https://pypi.internal.company.com/simple/ \
--trusted-host pypi.internal.company.comIn requirements.txt:
--index-url https://pypi.internal.company.com/simple/
--trusted-host pypi.internal.company.com
my-private-package==1.2.3
requests>=2.28.0Step 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 listto verify the effective configuration before troubleshooting - Pin the
certpath in CI/CD pipeline configuration, not in developer machines - Add a health check in Docker builds:
RUN pip install --dry-run my-private-packageto catch TLS issues early - Use
pip downloadto pre-download packages and cache them, reducing registry dependency - Set
PIP_DISABLE_PIP_VERSION_CHECK=1in 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