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
pip install certifi
Output: (none — exits 0 on success)
uv add certifi
Output: dependency resolved + added to pyproject.toml
poetry add certifi
Output: updated lockfile + virtualenv install
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
certifiversions. - 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 —
certifihas zero runtime dependencies. This is intentional; it must work even when the dependency resolver is broken.
Alternatives
| Package | Trade-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. |
truststore | Use the OS trust store from Python without an explicit path. Standardized in pip 23+ and growing in requests 2.32+. |
pip-system-certs | Patches requests to use the OS trust store. Older approach; truststore is preferred today. |
| Hand-maintained PEM | Use only for tightly controlled environments (e.g. on-prem datacenter with private CA). |
Common gotchas
- 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
certifiunless you append them to the file or usetruststore/REQUESTS_CA_BUNDLE. - Don't edit
cacert.pemin-place. It lives inside the installed package directory;pipwill overwrite it on the next upgrade. UseREQUESTS_CA_BUNDLE/SSL_CERT_FILEto point at an external PEM that includes your additions. 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)).- 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.
- Removing trust (CA distrust) takes effect when you upgrade
certifi. Old apps pinned to a year-oldcertifimay still trust a since-revoked CA. - Air-gapped systems that can't
pip installneed 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.
import certifi
print(certifi.where())
Output:
/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.
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.
pip install -U certifi
python -c "import certifi; print(certifi.where(), certifi.__version__)"
Output:
/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.
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.
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
SSLContextonce.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.Sessionso it caches the context internally;requests.get(...)per call re-parses for each new session. load_default_certs()is preferred toload_verify_locations(cafile=...)intruststore-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
certifiin 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 removal —
certifidoes not track EV status; that's a UA concern.
Security considerations
- The bundle is the whole game. A compromised
certificould trust an attacker's CA; pin and verify package integrity (pip install --require-hashes). - Don't disable verification in production.
verify=Falseinrequestsis the single most common security regression — actively scan codebases for it. certifilags Mozilla by hours or days. If a CA is acutely distrusted, upgradecertifiimmediately; pin minimum version inrequirements.txt.- Custom bundle for corporate CAs — never hand-edit
cacert.peminside the package; build an external combined bundle and setSSL_CERT_FILE/REQUESTS_CA_BUNDLE. - OS-store vs
certifi. On macOS / Windows, the OS trust store is often more up-to-date thancertifi.truststoregives you that, at the cost of one more dependency. - Watch for CVE announcements in NSS —
certifireleases 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.
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— usescertifi.where()by default forverify=True.urllib3— usescertifiif installed; falls back to the OS bundle otherwise.httpx— same default asrequests.botocore/boto3—certififor the defaultverifyparameter.truststore— alternative that uses OS trust instead of certifi's bundle.pipitself — bundlescertifiinternally for verifying PyPI's TLS certificates.
Compatibility matrix
| Python | certifi | Notes |
|---|---|---|
| 3.7 | latest | Stable. |
| 3.8 | latest | Stable. |
| 3.9 | latest | Stable. |
| 3.10 | latest | Stable. |
| 3.11 | latest | Stable. |
| 3.12 | latest | Stable. |
| 3.13 | latest | Stable; 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.txtso security-relevant updates pull through:certifi>=2026.1.1. - Rebuild containers monthly to pick up bundle updates. Treat
certifilike an OS security update. - Set
SSL_CERT_FILEin 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
certifiupgrade. - 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
truststoreinstead. 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 UX —
certifidoesn't track EV status; that's not its problem space. - You want to verify code-signing certificates, not server TLS — completely different trust store;
certifidoesn't help.
Troubleshooting common errors
| Error / Symptom | Likely cause | Fix |
|---|---|---|
SSLCertVerificationError: unable to get local issuer certificate | Private CA not in certifi bundle | Set REQUESTS_CA_BUNDLE or SSL_CERT_FILE to a combined PEM. |
[SSL: CERTIFICATE_VERIFY_FAILED] even after pip install -U certifi | App reads a different bundle | Print certifi.where() at runtime; check SSL_CERT_FILE overrides. |
| Recently distrusted CA still trusted | Old certifi pinned | Upgrade certifi; rebuild container. |
| macOS: "Mac OSX SSL: Network Error" | Old Python's bundled certs | Run Install Certificates.command shipped with python.org installer OR install certifi and explicitly point apps at it. |
| Corporate proxy fails TLS | MITM proxy not trusted | Add the proxy's CA to a custom PEM and set REQUESTS_CA_BUNDLE. |
Long-running daemon won't pick up new certifi | Process started before upgrade | Restart the daemon. |
See also
- Concept: HTTP — protocol fundamentals
- Packages: pip-urllib3 — main consumer
- Packages: pip-requests — main consumer
- Official certifi repo