cheat sheet

certifi

Package-level reference for certifi on PyPI — what the bundle contains, install, integration with requests / urllib3 / ssl, alternatives.

certifi

What it is

certifi is a Python package that bundles a curated set of root certificate authority (CA) certificates — the same set Mozilla ships in Firefox. It exposes a single function, certifi.where(), that returns the filesystem path to a PEM-formatted CA bundle. Most of the HTTP ecosystem (requests, httpx, urllib3, botocore, kubernetes, …) reads this file to validate TLS server certificates.

Reach for certifi directly when you need to: tell a non-HTTP library (raw ssl, pycurl, psycopg, pymongo) where the trusted root certificates live; ship a script that should not depend on the OS trust store; or update the bundle out-of-band from Python's release schedule.

Install

bash
pip install certifi

Output: (none — exits 0 on success)

bash
uv add certifi

Output: dependency resolved + added to pyproject.toml

bash
poetry add certifi

Output: updated lockfile + virtualenv install

bash
python -m certifi

Output: prints the absolute path to the bundled cacert.pem (also writable via certifi.where()).

Versioning & Python support

  • Versioning is calendar-based: YYYY.MM.DD (e.g. 2024.7.4, 2025.1.31). Each release corresponds to a snapshot of Mozilla's certificate store at that date.
  • Releases happen several times per year, usually within days of a Mozilla NSS update.
  • Supports Python 3.7+ on recent wheels; older Pythons can install older certifi versions.
  • The bundle inclusion criteria mirror Mozilla's — see the Mozilla NSS root program for what's added/removed.

Package metadata

  • Maintainer: Kenneth Reitz / Python Packaging Authority
  • Project home: github.com/certifi/python-certifi
  • Docs: pypi.org/project/certifi
  • License: MPL-2.0 (the bundle); the Python wrapper is MIT-licensed
  • Governance: Python Packaging Authority (PyPA)
  • First released: 2014
  • Downloads: consistently top 5 on PyPI; transitively required by virtually every HTTP-touching library

Package metadata details

  • Single module (certifi) with effectively three public symbols: where(), contents(), and __version__.
  • The bundle itself is a regular PEM file shipped inside the wheel; opening it with open() works like any text file.
  • Total bundle size ~250 KB and ~130 trusted roots in recent releases.

Optional dependencies & extras

  • None — certifi has zero runtime dependencies. This is intentional; it must work even when the dependency resolver is broken.

Alternatives

PackageTrade-off
OS trust store (/etc/ssl/certs/ca-certificates.crt on Debian/Ubuntu, macOS Keychain, Windows certstore)Reflects local CA changes (corporate roots, removed compromised CAs). Use when you control the host.
truststoreUse the OS trust store from Python without an explicit path. Standardized in pip 23+ and growing in requests 2.32+.
pip-system-certsPatches requests to use the OS trust store. Older approach; truststore is preferred today.
Hand-maintained PEMUse only for tightly controlled environments (e.g. on-prem datacenter with private CA).

Common gotchas

  1. The bundle is a Mozilla snapshot, not the OS trust. Corporate proxies' CA roots that you've installed in the OS won't be trusted by certifi unless you append them to the file or use truststore / REQUESTS_CA_BUNDLE.
  2. Don't edit cacert.pem in-place. It lives inside the installed package directory; pip will overwrite it on the next upgrade. Use REQUESTS_CA_BUNDLE / SSL_CERT_FILE to point at an external PEM that includes your additions.
  3. certifi.where() returns a string, not a file object. Pass it to libraries that take a path (e.g. requests.get(verify=path), ssl.SSLContext.load_verify_locations(cafile=path)).
  4. Calendar-dating misleads people. A release dated 2025.6.30 does NOT contain roots issued after that date; it contains the Mozilla snapshot as of that date.
  5. Removing trust (CA distrust) takes effect when you upgrade certifi. Old apps pinned to a year-old certifi may still trust a since-revoked CA.
  6. Air-gapped systems that can't pip install need to ship the wheel manually; the bundle is shipped inside the wheel, not downloaded at runtime.

Real-world recipes

The four recipes below cover the entire useful surface of certifi. Anything more elaborate is downstream library territory.

Recipe 1 — Print the bundle path.

python
import certifi
print(certifi.where())

Output:

swift
/home/alice/.venv/lib/python3.12/site-packages/certifi/cacert.pem

(exact path varies by venv / install location). Equivalent CLI: python -m certifi.

Recipe 2 — Use the bundle in a custom requests.Session.

python
import certifi, requests

s = requests.Session()
s.verify = certifi.where()      # explicit; `requests` already uses it as default
r = s.get("https://api.example.com/", timeout=5)
print(r.status_code)

Output: 200. Explicit verify lets you swap to a different bundle without code changes — e.g. s.verify = "/etc/ssl/corp-roots.pem".

Recipe 3 — Update the bundle and verify the new path.

bash
pip install -U certifi
python -c "import certifi; print(certifi.where(), certifi.__version__)"

Output:

swift
/home/alice/.venv/lib/python3.12/site-packages/certifi/cacert.pem 2026.4.26

The path stays the same across upgrades within one venv; only the contents and version change.

Recipe 4 — Use the bundle with raw ssl.SSLContext.

python
import certifi, ssl, socket

ctx = ssl.create_default_context(cafile=certifi.where())
with socket.create_connection(("api.example.com", 443)) as sock:
    with ctx.wrap_socket(sock, server_hostname="api.example.com") as ssock:
        print(ssock.version())     # e.g. 'TLSv1.3'
        ssock.send(b"GET / HTTP/1.1\r\nHost: api.example.com\r\nConnection: close\r\n\r\n")
        print(ssock.recv(200))

Output: TLSv1.3 followed by the first 200 bytes of the HTTP response. cafile is the only knob you need to tell the stdlib ssl module which CAs to trust.

Recipe 5 — Combine certifi with a corporate CA.

python
import certifi
from pathlib import Path

# One-time setup: build a combined PEM with the corp CA appended.
combined = Path("/etc/ssl/combined-roots.pem")
combined.write_text(Path(certifi.where()).read_text() + Path("/etc/ssl/corp-root.pem").read_text())

# Then use the combined bundle:
import os; os.environ["SSL_CERT_FILE"] = str(combined)

Output: any library that respects SSL_CERT_FILE (most do) now trusts both Mozilla roots and your corporate CA.

Performance tuning

certifi itself has no runtime perf surface — it's a path lookup. The interesting perf concern is repeated CA bundle parsing.

  • Build SSLContext once. ssl.create_default_context(cafile=certifi.where()) parses the 130-root PEM file. Repeating this per request is wasteful — cache the context at module level.
  • Reuse requests.Session so it caches the context internally; requests.get(...) per call re-parses for each new session.
  • load_default_certs() is preferred to load_verify_locations(cafile=...) in truststore-based code paths — system trust is loaded lazily by the OS.

Version migration guide

certifi follows Mozilla NSS — there are no API breaks. Migration notes are about the bundle contents, not the Python surface.

  • Roots removed from Mozilla NSS propagate to certifi in the next release. Examples in recent years: e-Tugra, TrustCor, DigiNotar (long ago).
  • Roots added for new CA programs (Let's Encrypt's ISRG Root X2, etc.) show up similarly.
  • Algorithm deprecations — Mozilla periodically drops SHA-1-signed roots; if you depend on a private CA still using SHA-1, you'll need to maintain your own bundle.
  • EV indicator removalcertifi does not track EV status; that's a UA concern.

Security considerations

  • The bundle is the whole game. A compromised certifi could trust an attacker's CA; pin and verify package integrity (pip install --require-hashes).
  • Don't disable verification in production. verify=False in requests is the single most common security regression — actively scan codebases for it.
  • certifi lags Mozilla by hours or days. If a CA is acutely distrusted, upgrade certifi immediately; pin minimum version in requirements.txt.
  • Custom bundle for corporate CAs — never hand-edit cacert.pem inside the package; build an external combined bundle and set SSL_CERT_FILE / REQUESTS_CA_BUNDLE.
  • OS-store vs certifi. On macOS / Windows, the OS trust store is often more up-to-date than certifi. truststore gives you that, at the cost of one more dependency.
  • Watch for CVE announcements in NSS — certifi releases follow shortly after.

Testing & CI

certifi is rarely tested directly; tests usually mock certifi.where() only when overriding the bundle path. The most useful CI check is freshness — fail builds when certifi is more than a few months stale.

bash
python -c "import certifi, datetime; v = certifi.__version__; \
print(v); year, month, _ = v.split('.'); \
assert (datetime.date.today() - datetime.date(int(year), int(month), 1)).days < 180, 'certifi too old'"

Output: prints the version and exits 0 if the bundle is fresher than 180 days; exits 1 otherwise — wire into CI to keep deployments current.

Ecosystem integrations

  • requests — uses certifi.where() by default for verify=True.
  • urllib3 — uses certifi if installed; falls back to the OS bundle otherwise.
  • httpx — same default as requests.
  • botocore/boto3certifi for the default verify parameter.
  • truststore — alternative that uses OS trust instead of certifi's bundle.
  • pip itself — bundles certifi internally for verifying PyPI's TLS certificates.

Compatibility matrix

PythoncertifiNotes
3.7latestStable.
3.8latestStable.
3.9latestStable.
3.10latestStable.
3.11latestStable.
3.12latestStable.
3.13latestStable; wheel available immediately.

certifi is a pure-data wheel — wheel availability is universal across Python versions and platforms.

Production deployment

  • Pin a minimum version in requirements.txt so security-relevant updates pull through: certifi>=2026.1.1.
  • Rebuild containers monthly to pick up bundle updates. Treat certifi like an OS security update.
  • Set SSL_CERT_FILE in the container environment when you need to inject corporate roots — don't bake a custom PEM into the image at the certifi path.
  • Monitor for upstream CA distrust events (TrustCor 2022, e-Tugra 2023, etc.). When Mozilla announces a removal, plan an emergency certifi upgrade.
  • Document why for any custom CA bundle — undocumented verify="/some/path" is a deployment-time landmine.

When NOT to use this

  • You want the OS trust store — use truststore instead. It's the modern recommendation, especially for corporate environments with custom CAs.
  • You're on an air-gapped, OS-managed CA setup — your own PEM is the right answer, not Mozilla's.
  • You need EV certificate UXcertifi doesn't track EV status; that's not its problem space.
  • You want to verify code-signing certificates, not server TLS — completely different trust store; certifi doesn't help.

Troubleshooting common errors

Error / SymptomLikely causeFix
SSLCertVerificationError: unable to get local issuer certificatePrivate CA not in certifi bundleSet REQUESTS_CA_BUNDLE or SSL_CERT_FILE to a combined PEM.
[SSL: CERTIFICATE_VERIFY_FAILED] even after pip install -U certifiApp reads a different bundlePrint certifi.where() at runtime; check SSL_CERT_FILE overrides.
Recently distrusted CA still trustedOld certifi pinnedUpgrade certifi; rebuild container.
macOS: "Mac OSX SSL: Network Error"Old Python's bundled certsRun Install Certificates.command shipped with python.org installer OR install certifi and explicitly point apps at it.
Corporate proxy fails TLSMITM proxy not trustedAdd the proxy's CA to a custom PEM and set REQUESTS_CA_BUNDLE.
Long-running daemon won't pick up new certifiProcess started before upgradeRestart the daemon.

See also