httpx ConnectError: SSL CERTIFICATE_VERIFY_FAILED fix (tested)

httpx checks certificates against certifi's bundle and raises httpx.ConnectError: [SSL: CERTIFICATE_VERIFY_FAILED] for a private or self-signed CA. Pass verify=ssl.create_default_context(cafile="ca.pem"), or set SSL_CERT_FILE=ca.pem.

httpx reports a TLS verification failure as httpx.ConnectError, not as an SSLError. Measured with httpx 0.28.1 on Python 3.13.5 against a local HTTPS server using a self-signed certificate (cert.pem, made with openssl req -x509); the same fix applies to a corporate proxy or private CA.

1. The error

import httpx
from srv import start
base = start()   # HTTPS server with a self-signed certificate (cert.pem)

try:
    httpx.get(base)
except httpx.ConnectError as e:
    print(type(e).__name__ + ":", e)

Output:

ConnectError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate (_ssl.c:1029)

httpx verifies against the certifi CA bundle by default, not the operating system store, so a certificate your browser or OS trusts (for example a company root CA installed system-wide) can still fail in httpx.

2. Fix: pass an SSLContext that trusts your CA

import httpx, ssl
from srv import start
base = start()

ctx = ssl.create_default_context(cafile="cert.pem")   # your CA / corporate root
print(httpx.get(base, verify=ctx).status_code)

with httpx.Client(verify=ctx) as client:
    print(client.get(base).text)

Output:

200
ok

verify= accepts an ssl.SSLContext, per request or on the Client / AsyncClient. Point cafile at the CA certificate (PEM) that signed the server certificate, or at the server certificate itself when it is self-signed.

3. verify="path.pem" still works but is deprecated

import httpx, warnings
from srv import start
base = start()
warnings.simplefilter("always")

r = httpx.get(base, verify="cert.pem")   # string path: still works in 0.28, but deprecated
print(r.status_code)

Output:

/tmp/fx384/ve/lib/python3.13/site-packages/httpx/_config.py:51: DeprecationWarning: `verify=<str>` is deprecated. Use `verify=ssl.create_default_context(cafile=...)` or `verify=ssl.create_default_context(capath=...)` instead.
  warnings.warn(message, DeprecationWarning)
200

In httpx 0.28 a string path is accepted with a DeprecationWarning pointing to ssl.create_default_context(cafile=...); switch to the SSLContext form before it is removed.

4. Without code changes: SSL_CERT_FILE

import os, httpx, subprocess, sys
from srv import start
base = start()

code = f"import httpx; print(httpx.get({base!r}).status_code)"
env = dict(os.environ, SSL_CERT_FILE="cert.pem")
out = subprocess.run([sys.executable, "-c", code], env=env, capture_output=True, text=True)
print("SSL_CERT_FILE=cert.pem ->", out.stdout.strip() or out.stderr.strip().splitlines()[-1])

Output:

SSL_CERT_FILE=cert.pem -> 200

With the default trust_env=True, httpx uses SSL_CERT_FILE (or SSL_CERT_DIR) instead of certifi when it is set. Useful when the request is made inside a library you don't control. The file replaces the bundle, so it must contain every CA you need.

5. verify=False (testing only)

import httpx
from srv import start
base = start()

r = httpx.get(base, verify=False)   # no verification at all: testing only
print(r.status_code)

Output:

200

This disables certificate checking completely, so anyone in the middle can read and change the traffic. Use it only against a local test server.

Related: Python SSL CERTIFICATE_VERIFY_FAILED, self-signed certificate in certificate chain, httpx timeout, httpx follow redirects.

Frequently asked questions

Why does httpx raise ConnectError for an SSL problem?

httpx wraps TLS handshake failures in httpx.ConnectError; the message shows [SSL: CERTIFICATE_VERIFY_FAILED] and the reason, such as self-signed certificate.

Does httpx use the system certificate store?

No. By default it uses the certifi bundle, unless SSL_CERT_FILE or SSL_CERT_DIR is set (with trust_env=True, the default).

Is verify='/path/ca.pem' still supported in httpx?

In httpx 0.28.1 it works but emits a DeprecationWarning; use verify=ssl.create_default_context(cafile=...) instead.

Written with AI assistance. Measured on 2026-10-23 with httpx 0.28.1 on Python 3.13.5 against a local HTTPS http.server with a self-signed certificate. Each block was run verbatim and the output pasted unedited. PlainHeaders is our extension; httpx is free.