# Python — CERTIFICATE_VERIFY_FAILED

`SSLCertificateVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED]` means Python could not build a trusted path to the certificate a server presented. On macOS the usual cause is that Python ships its own CA bundle via the `certifi` package and does not read the system keychain — run the bundled `Install Certificates.command`. Elsewhere it is normally a corporate TLS-inspecting proxy or a private CA whose root is missing; point Python at a bundle containing it with the `SSL_CERT_FILE` or `REQUESTS_CA_BUNDLE` environment variable. Never fix it with `verify=False`.

Source: https://certguru.io/errors/certificate-verify-failed-python/
Last updated: 2026-08-19
Commands verified against OpenSSL 3.5.7.

---

> **What it looks like**
>
> ```
>   requests.exceptions.SSLError: HTTPSConnectionPool(host='example.com', port=443):
>   Max retries exceeded with url: / (Caused by SSLError(SSLCertVerificationError(1,
>   '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local
>   issuer certificate (_ssl.c:1006)')))
>   ```

## Step 1 — is it Python, or is it the server?

Test the server with a tool that has nothing to do with Python. If this reports a problem, the
server is misconfigured and no Python change will help:

```bash
openssl s_client -connect example.com:443 -servername example.com /dev/null \
  | grep "Verify return code"
```

Expected output:

```
Verify return code: 0 (ok)
```

Code 20 means the server is not sending its intermediates — see
[that error](/errors/unable-to-get-local-issuer-certificate/). Code 0 means the server is fine and
the problem is local to Python.

## Step 2 — see which bundle Python is actually using

```python
import ssl, certifi
print(ssl.get_default_verify_paths())
print(certifi.where())
```

Then confirm the failing certificate really is absent from it:

```bash
openssl s_client -connect example.com:443 -servername example.com \
  -CAfile "$(python -c 'import certifi; print(certifi.where())')" /dev/null \
  | grep "Verify return code"
```

## macOS — the most common cause by far

A python.org installation on macOS does **not** read the system keychain. It expects certifi's
bundle to be installed by a script that does not run automatically:

```bash
/Applications/Python\ 3.12/Install\ Certificates.command
```

Adjust the version number to match. With Homebrew Python, reinstalling `certifi` usually achieves
the same thing:

```bash
pip install --upgrade certifi
```

## Corporate proxies and private CAs

If the chain ends at a root your organisation controls, that root has to be in the bundle Python
uses. Appending it to certifi's bundle works but is undone by the next upgrade, so prefer a separate
file:

```bash
cat "$(python -c 'import certifi; print(certifi.where())')" corporate-root.crt > /etc/ssl/certs/python-bundle.crt
export SSL_CERT_FILE=/etc/ssl/certs/python-bundle.crt
export REQUESTS_CA_BUNDLE=/etc/ssl/certs/python-bundle.crt
```

| Variable | Read by |
| --- | --- |
| `SSL_CERT_FILE` | `ssl`, `urllib`, most stdlib TLS |
| `REQUESTS_CA_BUNDLE` | `requests` |
| `CURL_CA_BUNDLE` | `requests` also honours this |
| `pip config set global.cert` | pip specifically |

Or per call, which is explicit and does not depend on the environment:

```python
import requests
requests.get("https://example.com", verify="/etc/ssl/certs/python-bundle.crt")
```

## In a container

```dockerfile
COPY corporate-root.crt /usr/local/share/ca-certificates/
RUN update-ca-certificates
ENV SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
ENV REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
```

`update-ca-certificates` alone is not enough — Python will keep using certifi's bundle unless the
environment variables point it elsewhere.

## What not to do

> **verify=False is not a fix**
>
> ```python
>   requests.get(url, verify=False)          # do not ship this
>   ssl._create_default_https_context = ssl._create_unverified_context  # nor this
>   ```
>   Both turn off the check that produced the error. The traffic stays encrypted and becomes
>   unauthenticated, which means anything on the network path can read and modify it while the code
>   reports success.


## Frequently asked questions

### Why does curl work but Python fail on the same URL?

They use different trust stores. curl normally uses the system one; Python uses whatever `certifi` ships, or whatever `SSL_CERT_FILE` points at. A root installed for the system is invisible to Python unless it is in that bundle too.

### Is verify=False acceptable?

No. It disables verification for that request entirely, so the connection is encrypted but unauthenticated and can be transparently intercepted. Use it once to confirm a diagnosis, never in code that ships.

### How do I fix this on macOS?

Run the `Install Certificates.command` script inside `/Applications/Python 3.x/`. It installs certifi's bundle where Python expects it. A fresh python.org install ships without a usable trust store until you do.

### Why does pip fail but python work?

pip may be using a different bundle, particularly inside a virtualenv or a corporate build image. Set it explicitly with `pip config set global.cert /path/to/bundle.crt`.


## References

- [Python ssl module](https://docs.python.org/3/library/ssl.html) — how Python locates a trust store, and what create_default_context does
- [RFC 5280: X.509 and CRL profile](https://www.rfc-editor.org/rfc/rfc5280) — the specification every field on a certificate comes from

## Related pages

- https://certguru.io/errors/self-signed-certificate-in-chain/
- https://certguru.io/errors/unable-to-get-local-issuer-certificate/
- https://certguru.io/errors/unable-to-verify-leaf-signature/
- https://certguru.io/errors/curl-60-ssl-certificate-problem/
- https://certguru.io/concepts/certificate-chain/
- https://certguru.io/openssl/get-certificate-from-server/
- https://certguru.io/openssl/verify-certificate-chain/

--
CertGuru — https://certguru.io
