Skip to content

Fix: Python SSL: CERTIFICATE_VERIFY_FAILED

FixDevs ·

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.command

Replace 3.11 with your Python version. This runs:

pip install --upgrade certifi
/Applications/Python\ 3.11/python3 -m certifi

After 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 certifi package 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 certifi

Fix 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_bundle

Option 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.py

Fix 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=False to bypass SSL verification. This disables certificate checking entirely, making your connection vulnerable to man-in-the-middle attacks. Never use verify=False in 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-certificates

On Alpine Linux (common in Docker):

apk add --no-cache ca-certificates
update-ca-certificates

In 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.pem

Fix 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.pem

Or 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 -showcerts

This 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.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles