Fix: Python SSL: CERTIFICATE_VERIFY_FAILED
Quick Answer
How to fix Python SSL CERTIFICATE_VERIFY_FAILED error caused by missing root certificates on macOS, expired system certs, corporate proxies, and self-signed certificates in requests, urllib, and httpx.
The Error
You run a Python script that makes an HTTPS request and get:
ssl.SSLCertificateError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1129)Or through the requests library:
requests.exceptions.SSLError: HTTPSConnectionPool(host='api.example.com', port=443):
Max retries exceeded with url: /data (Caused by SSLError(SSLCertificateError("bad handshake:
Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')])")))Or via urllib:
urllib.error.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed:
unable to get local issuer certificate (_ssl.c:1129)>Or after installing Python on macOS:
ssl.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:852)Note: This error is distinct from the pip-specific SSL error (pip install fails). This article covers SSL errors when Python scripts make HTTPS requests at runtime. For pip install SSL failures, see Fix: pip SSL Certificate Verify Failed.
Why This Happens
When Python makes an HTTPS connection, it verifies the server’s SSL certificate against a set of trusted root certificates (a “CA bundle”). The error means Python could not find a trusted root certificate to verify the server’s certificate chain.
Common causes:
- macOS Python installation: The official Python installer from python.org does NOT use macOS’s system certificate store. It ships with no CA bundle configured, causing all HTTPS requests to fail until you run a certificate install script.
- Corporate proxy / firewall: Your company’s network intercepts HTTPS traffic and presents its own certificate, which Python does not trust.
- Self-signed or private CA certificates: Connecting to an internal server with a self-signed cert or one issued by a private CA.
- Outdated system CA bundle: The CA bundle bundled with Python or OpenSSL is outdated and missing newer root certificates.
- Virtual environment isolation: A virtualenv uses different certificate paths than the system Python.
- Docker containers: Minimal base images often have no CA certificates installed.
Fix 1: Run the Certificate Install Script (macOS)
This is the most common cause on macOS. The official Python.org installer ships with an Install Certificates.command script that installs the certifi CA bundle.
Open Finder and navigate to:
/Applications/Python 3.x/Double-click Install Certificates.command. Or run it from the terminal:
/Applications/Python\ 3.11/Install\ Certificates.commandReplace 3.11 with your Python version. This runs:
pip install --upgrade certifi
/Applications/Python\ 3.11/python3 -m certifiAfter running this, retry your script. This fix resolves the error for the vast majority of macOS users.
Why this matters: Python on macOS uses its own bundled OpenSSL rather than the system’s Security framework. The system’s certificate store (used by Safari, curl, etc.) is not accessible to Python by default. The
certifipackage provides an up-to-date Mozilla CA bundle that Python can use.
Fix 2: Use certifi Explicitly in Your Code
If you cannot run the install script (e.g., on a server), point Python to the certifi CA bundle explicitly:
With requests:
import requests
import certifi
response = requests.get("https://api.example.com/data", verify=certifi.where())
print(response.json())With urllib:
import urllib.request
import ssl
import certifi
context = ssl.create_default_context(cafile=certifi.where())
with urllib.request.urlopen("https://api.example.com/data", context=context) as response:
print(response.read())Set it globally for all requests in a session:
import requests
import certifi
import os
# Set the environment variable for the entire process
os.environ["REQUESTS_CA_BUNDLE"] = certifi.where()
os.environ["SSL_CERT_FILE"] = certifi.where()Install certifi if you do not have it:
pip install certifiFix 3: Fix Corporate Proxy / Man-in-the-Middle Certificates
If your company uses a proxy that intercepts HTTPS traffic, Python sees the proxy’s certificate instead of the server’s. Python does not trust the proxy’s self-signed or corporate CA certificate.
Option A: Add the corporate certificate to your trusted CA bundle:
Get the corporate root certificate file (ask your IT department — it is usually a .pem or .crt file). Then:
import requests
response = requests.get(
"https://internal.company.com/api",
verify="/path/to/corporate-root-ca.pem"
)Or combine it with certifi’s bundle:
import certifi
import shutil
import os
# Append corporate cert to certifi's bundle
corporate_cert = "/path/to/corporate-root-ca.pem"
certifi_bundle = certifi.where()
# Create a combined bundle
combined_bundle = "/tmp/combined-ca-bundle.pem"
shutil.copy(certifi_bundle, combined_bundle)
with open(corporate_cert, "r") as corp, open(combined_bundle, "a") as bundle:
bundle.write(corp.read())
os.environ["REQUESTS_CA_BUNDLE"] = combined_bundleOption B: Set the certificate via environment variable:
export REQUESTS_CA_BUNDLE=/path/to/corporate-root-ca.pem
export SSL_CERT_FILE=/path/to/corporate-root-ca.pem
python your_script.pyFix 4: Fix Self-Signed Certificates for Internal Servers
If you are connecting to a server with a self-signed certificate (common in development environments):
Pass the certificate file directly:
import requests
# Verify against the server's self-signed cert
response = requests.get(
"https://localhost:8443/api",
verify="/path/to/server-cert.pem"
)For mutual TLS (client certificate authentication):
response = requests.get(
"https://internal-api.company.com/data",
verify="/path/to/ca-bundle.pem",
cert=("/path/to/client-cert.pem", "/path/to/client-key.pem")
)Common Mistake: Using
verify=Falseto bypass SSL verification. This disables certificate checking entirely, making your connection vulnerable to man-in-the-middle attacks. Never useverify=Falsein production code. It suppresses the error but does not fix it.
If you must use verify=False in development (not recommended), suppress the InsecureRequestWarning:
import requests
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
response = requests.get("https://localhost:8443/api", verify=False)Fix 5: Update Certificates on Linux / Docker
On Debian/Ubuntu-based systems:
sudo apt-get update && sudo apt-get install -y ca-certificates
sudo update-ca-certificatesOn Alpine Linux (common in Docker):
apk add --no-cache ca-certificates
update-ca-certificatesIn a Dockerfile:
FROM python:3.11-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# Or install certifi and set the env var
RUN pip install certifi
ENV SSL_CERT_FILE=/usr/local/lib/python3.11/site-packages/certifi/cacert.pemFix 6: Fix SSL in Virtual Environments
Virtual environments sometimes do not inherit the system’s certificate configuration:
# Activate your virtualenv
source venv/bin/activate
# Install certifi inside the virtualenv
pip install certifi
# Check which cert file Python is using
python -c "import ssl; print(ssl.get_default_verify_paths())"Set the cert path for the virtualenv:
export SSL_CERT_FILE=$(python -m certifi)
export REQUESTS_CA_BUNDLE=$(python -m certifi)Add these exports to your .env file or shell profile to make them persistent. For .env loading issues, see Fix: .env variables not loading.
Fix 7: Fix httpx and Other HTTP Libraries
The fix applies similarly to other HTTP libraries:
httpx:
import httpx
import certifi
import ssl
ssl_context = ssl.create_default_context(cafile=certifi.where())
with httpx.Client(verify=ssl_context) as client:
response = client.get("https://api.example.com/data")aiohttp:
import aiohttp
import ssl
import certifi
async def fetch():
ssl_context = ssl.create_default_context(cafile=certifi.where())
async with aiohttp.ClientSession() as session:
async with session.get("https://api.example.com/data", ssl=ssl_context) as response:
return await response.json()boto3 (AWS SDK):
export AWS_CA_BUNDLE=/path/to/corporate-ca.pemOr in code:
import boto3
session = boto3.Session()
client = session.client("s3", verify="/path/to/ca-bundle.pem")Diagnose the Certificate Chain
Before applying a fix, identify exactly which certificate is failing:
import ssl
import socket
hostname = "api.example.com"
port = 443
context = ssl.create_default_context()
try:
with socket.create_connection((hostname, port)) as sock:
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
cert = ssock.getpeercert()
print("Certificate is valid")
print("Subject:", cert["subject"])
print("Issuer:", cert["issuer"])
print("Expires:", cert["notAfter"])
except ssl.SSLCertificateError as e:
print("SSL Error:", e)Or use OpenSSL from the command line:
openssl s_client -connect api.example.com:443 -showcertsThis shows the full certificate chain and which CA issued the certificate, helping you determine whether to update certifi, add a corporate cert, or contact the server admin.
Still Not Working?
Check if the site itself has a certificate issue. Use an online SSL checker (e.g., SSL Labs) to verify the server’s certificate is valid and properly chained. If the server is misconfigured, the fix is on the server side.
Check for clock skew. SSL certificates have expiration dates. If your system clock is significantly off, certificates may appear expired or not yet valid. Sync your system clock: sudo ntpdate pool.ntp.org.
Check Python’s OpenSSL version. Older OpenSSL versions bundled with Python may not support newer TLS extensions:
python -c "import ssl; print(ssl.OPENSSL_VERSION)"If you see OpenSSL 1.0.x, upgrade Python to a version that ships with OpenSSL 1.1.x or 3.x.
Check for SNI issues. Some servers require SNI (Server Name Indication) to present the correct certificate. Python 3.x supports SNI by default. If you are on Python 2 (end of life), the pyOpenSSL and ndg-httpsclient packages add SNI support.
For connection errors that happen before SSL negotiation, see Fix: Python ConnectionError: Max Retries Exceeded.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Python requests.get() Hanging — Timeout Not Working
How to fix Python requests hanging forever — why requests.get() ignores timeout, how to set connect and read timeouts correctly, use session-level timeouts, and handle timeout exceptions properly.
Fix: Flask Route Returns 404 Not Found
How to fix Flask routes returning 404 — trailing slash redirect, Blueprint prefix issues, route not registered, debug mode, and common URL rule mistakes.
Fix: pandas merge() Key Error and Duplicate Columns (_x, _y)
How to fix pandas merge and join errors — KeyError on merge key, duplicate _x/_y columns, unexpected row counts, suffixes, and how to validate merge results.
Fix: Certbot Certificate Renewal Failed (Let's Encrypt)
How to fix Certbot certificate renewal failures — domain validation errors, port 80 blocked, nginx config issues, permissions, and automating renewals with systemd or cron.