By the end of this guide you will know which of four causes is breaking your encrypted connection, and you will have fixed it properly in about fifteen minutes. You will also have a one-line test that tells you, before you touch any code, whether the problem is your machine or the API you are calling. Everything here works the same whether you are calling an AI model, downloading a file, or installing a package.
This is one guide in Debugging Python AI Errors, the section that walks through the errors beginners actually hit when their first AI script meets the real internet.
What the error is actually telling you
The full message usually looks like this, buried at the bottom of a long traceback:
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify
failed: unable to get local issuer certificate (_ssl.c:1006)
Read it as a sentence about your computer, not about the server. When Python opens an https:// address it performs a TLS handshake — a short exchange where the server proves who it is by presenting a certificate, and your machine decides whether to believe it. That certificate is signed by an intermediate authority, which is signed by a root certificate authority (a small number of organisations whose certificates are shipped with operating systems and browsers). Python walks that chain upward and looks for the root in its own trust store: a plain text file full of trusted root certificates. If the chain does not end somewhere in that file, Python refuses the connection. It never sends your request, so your API key is never used and nothing is billed.
The important consequence is that the fix is always local. You are repairing the file Python reads, or telling Python to read a different one.
Prerequisites
You need Python 3.10 or newer and a project folder you can run scripts from. If Python is not installed yet, pick the guide for your platform: How to Install Python for AI Projects on Mac, How to Install Python for AI on Windows, or How to Install Python for AI on Linux.
Work inside a virtual environment (an isolated folder holding this project's packages) so the certificate package you upgrade below affects only this project. Create a Python Virtual Environment for AI covers it in detail:
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install "httpx>=0.27" "openai>=1.40" "python-dotenv>=1.0" certifi
Put your key in a .env file in the project folder:
OPENAI_API_KEY=sk-your-key-here
Then add that file to .gitignore before you write another line:
echo ".env" >> .gitignore
This matters more than usual on this page, because the wrong "fix" for a certificate error is exactly what turns a private key into a public one.
Step 1 — Prove it is TLS, not the API
Before changing anything, separate a broken trust store from a broken request. Run this script, which fetches a well-known public page and prints the file Python is using as its trust store:
import ssl
import certifi
import httpx
print("OpenSSL build:", ssl.OPENSSL_VERSION)
print("Trust store file:", certifi.where())
try:
response = httpx.get("https://www.python.org", timeout=10.0)
print("Handshake OK, HTTP status:", response.status_code)
except httpx.ConnectError as exc:
print("Handshake failed:", exc)
A printed status of 200 means TLS is healthy and your problem lies in the API call itself. A Handshake failed line means the trust store is the problem, and every HTTPS address will fail the same way.
Now narrow it further by hitting the API host with no key at all:
import httpx
try:
response = httpx.get("https://api.openai.com/v1/models", timeout=10.0)
print("Reached the API. HTTP status:", response.status_code)
except httpx.ConnectError as exc:
print("Could not complete the handshake:", exc)
An HTTP status of 401 is good news here. It means the handshake succeeded and the server answered — it simply refused an unauthenticated request. Your certificates are fine and you have an authentication problem instead, covered in Fix the 401 Unauthorized Error in OpenAI Python. If instead you see a certificate message, keep going.
The two results split the remaining work cleanly. If every host fails, your local trust store is stale or empty. If only the API host fails while python.org succeeds, something on your network is sitting between you and that host and re-signing the traffic.
Step 2 — Run the macOS certificate installer
This is the single most common cause, and it catches almost everyone who downloads Python from python.org onto a Mac. That build does not read the certificates in macOS Keychain. It expects to use its own bundled list, and the installer leaves that list unlinked until you run a small script it ships with. Until you do, every HTTPS call from that interpreter fails.
Find your Python folder in Applications and run the file:
ls /Applications | grep "Python"
/Applications/Python\ 3.12/Install\ Certificates.command
Substitute your own version number for 3.12. The script prints a few lines, installs certifi, and links Python's default certificate file to it. Re-run the Step 1 test afterwards; it should print 200.
If there is no Python 3.x folder in Applications, you did not use the python.org installer — you are on Homebrew, pyenv, or the system Python, and this step does not apply to you. Skip to Step 3. You can confirm which interpreter is active with:
which python
python -c "import sys; print(sys.prefix)"
Homebrew and pyenv builds link against your operating system's certificates or against certifi directly, so they rarely need this step. A tidy way to avoid the whole issue on a Mac is to install Python through Homebrew in the first place.
Step 3 — Upgrade certifi and pip
certifi is the Python package that carries the list of trusted root certificates. Because authorities are added and retired over time, an old copy can miss the issuer a modern API uses. Upgrading it is quick, safe, and fixes a surprising share of cases on machines that have been running the same environment for a year or more.
python -m pip install --upgrade pip certifi
python -c "import certifi; print(certifi.where())"
Confirm the file is real and populated rather than an empty placeholder:
import certifi
path = certifi.where()
with open(path, "rb") as handle:
data = handle.read()
print("Bundle:", path)
print("Size:", len(data), "bytes")
print("Certificates:", data.count(b"BEGIN CERTIFICATE"))
A healthy bundle is a few hundred kilobytes and holds well over a hundred certificates. A count of zero, or a FileNotFoundError, means the path Python thinks it should read does not exist — which is exactly what Step 2 repairs on macOS.
One subtlety worth knowing: pip itself uses the same trust machinery. If pip install fails with the same certificate error, you cannot upgrade certifi with pip to fix pip. Break the loop by fixing the operating system trust first (Step 2 on macOS, sudo apt install --reinstall ca-certificates on Debian and Ubuntu), or by passing the corporate certificate to pip directly as shown in the next step.
Step 4 — Point Python at a corporate root certificate
If only some hosts fail, a proxy or antivirus product on your network is performing TLS inspection: it terminates your encrypted connection, reads the traffic, then re-encrypts it with a certificate signed by your employer's own root authority. Browsers accept this because IT installed that root into the operating system. Python, reading certifi instead, has never heard of it.
Confirm the diagnosis by looking at who signed the certificate you are being served:
openssl s_client -showcerts -connect api.openai.com:443 </dev/null 2>/dev/null | grep "issuer="
If the issuer names your company, a security vendor, or your antivirus product rather than a public authority, that is your answer. Ask IT for the root certificate as a .pem file — it is not secret, and they hand it out routinely. Save it somewhere stable, then set two environment variables:
export SSL_CERT_FILE="$HOME/certs/corp-root-ca.pem"
export REQUESTS_CA_BUNDLE="$HOME/certs/corp-root-ca.pem"
export PIP_CERT="$HOME/certs/corp-root-ca.pem"
Add those three lines to ~/.zshrc or ~/.bashrc so they survive a new terminal. On Windows, use setx SSL_CERT_FILE "C:\certs\corp-root-ca.pem" in Command Prompt and open a fresh window.
Some libraries pin certifi internally and ignore those variables, so the most reliable approach in code is to build the trust settings yourself and hand them to the client. Because the openai package is built on httpx, you can pass a configured HTTP client straight into it:
import os
import ssl
import certifi
import httpx
from dotenv import load_dotenv
from openai import OpenAI
load_dotenv() # reads .env, which is listed in .gitignore
ca_file = os.getenv("SSL_CERT_FILE", certifi.where())
context = ssl.create_default_context(cafile=ca_file)
http_client = httpx.Client(verify=context, timeout=30.0)
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"), http_client=http_client)
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "Reply with the single word: connected."}],
)
print(response.choices[0].message.content)
Verification is still fully on here. You have only widened the set of issuers Python trusts to include the one your employer legitimately operates. If this script now succeeds but the calls take a long time or drop midway, the remaining problem is the network rather than the certificates, and Fix Connection and Timeout Errors with AI APIs picks up from there.
Never ship verify=False
Every forum thread about this error contains someone suggesting verify=False, and it does make the message go away. It also makes the encryption meaningless. With verification disabled, Python accepts literally any certificate from anyone — a compromised café router, a misconfigured proxy, an attacker on the same office network. That party can present its own certificate, decrypt everything you send, and read the Authorization header where your API key sits in plain text. They can then bill your account until you notice.
It is not a temporary shortcut either. Disabled verification has a habit of surviving in a repository, being copied into the deployment script, and running in production for months. Treat it as a line that never gets committed, and read Manage API Keys Safely in Production for the wider habit this belongs to.
Certificate settings quick reference
These are the levers worth knowing, in the order you would reach for them.
| Setting | Read by | Set it to |
|---|---|---|
SSL_CERT_FILE | Python's ssl module and most libraries | Absolute path to a PEM file |
REQUESTS_CA_BUNDLE | requests, and tools built on it | The same PEM path |
PIP_CERT | pip only, while installing | The same PEM path |
ssl.create_default_context(cafile=...) | Your own code, per client | Path chosen in Python |
A PEM file is plain text: you can open it and see blocks starting with BEGIN CERTIFICATE. If yours does not, it is in a different format and needs converting before Python will read it.
Troubleshooting
certificate verify failed: unable to get local issuer certificate— Python found no trusted root for the chain. Run the macOS installer from Step 2, then upgradecertifi.certificate verify failed: self signed certificate in certificate chain— a proxy or antivirus is re-signing your traffic. Add its root certificate and setSSL_CERT_FILEas in Step 4.certificate verify failed: certificate has expired— usually the clock, not the certificate. Check the output ofdate, turn on automatic network time, and retry.openai.APIConnectionError: Connection error.— the SDK wraps the real cause. Catch it and printexc.__cause__to reveal the underlyingSSLCertVerificationError; Read a Python Traceback in Five Minutes shows how to read the rest.FileNotFoundErrorafter settingSSL_CERT_FILE— the path is wrong or the variable is unquoted with a space in it. Runls -l "$SSL_CERT_FILE"to check.
When to use this vs. alternatives
- Fix the trust store when the whole machine is affected. Running the macOS installer or upgrading
certifirepairs every script and every tool at once, which is the right level for a laptop you own. - Use an explicit
SSLContextin code when only one project needs a special certificate. Building the context and passing it intohttpx.Clientkeeps the change visible in the repository and portable to a colleague who reads it. - Use environment variables when a whole machine sits behind one proxy. They apply to
pip, your editor's tooling, and every script without editing any of them, which is why IT departments hand out that instruction. - Never reach for
verify=False. There is no situation in this list where it is the shorter path to a working, safe result.
Work the causes in the order this guide presents them and you will rarely need more than two attempts: the macOS installer clears most cases outright, a certifi upgrade clears most of the rest, and anything that survives both is a proxy that needs one certificate file and one environment variable. If a script imports fail rather than a connection fail, the sibling guide Fix ModuleNotFoundError: No Module Named openai is the one you want. Back to Debugging Python AI Errors.
Related guides
- Debugging Python AI Errors — the main guide for this section, with every common first-script failure in one place.
- Fix Connection and Timeout Errors with AI APIs — what to do once the handshake works but the call still stalls.
- Read a Python Traceback in Five Minutes — find the real cause under a wrapped SDK error.
- Create a Python Virtual Environment for AI — keep each project's
certifiversion to itself. - Manage API Keys Safely in Production — the habits that keep a key private once your script works.