ssl.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed
Encountering
ssl.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failedmeans your Python application couldn't trust a server's SSL certificate; this guide explains how to fix it with practical, engineer-tested solutions.
What This Error Means
When you see ssl.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed, it indicates that your Python application (or underlying SSL library) tried to establish a secure connection, but couldn't verify the identity of the server it was trying to connect to. In essence, the client couldn't confirm that the server's SSL certificate was legitimate and issued by a trusted Certificate Authority (CA).
This isn't just a random hiccup; it's a security mechanism doing its job. The purpose of SSL/TLS certificate verification is to prevent Man-in-the-Middle (MITM) attacks. Without proper verification, a malicious actor could intercept your communication by presenting a fake certificate, and your client would unknowingly send sensitive data to them. So, while frustrating, this error is a warning sign that your connection isn't as secure as it should be, or that there's a misconfiguration preventing trust from being established.
Why It Happens
The core of the problem is a failure in the trust chain. When a client connects to a server over HTTPS, the server sends its SSL certificate. The client then tries to verify this certificate by checking:
1. Is the certificate signed by a trusted Certificate Authority (CA)? The client maintains a list of trusted root CAs. It attempts to trace the server's certificate back to one of these roots.
2. Is the certificate expired or revoked?
3. Does the hostname in the certificate match the hostname the client is trying to connect to? (Subject Alternative Names - SANs).
If any of these checks fail, the CERTIFICATE_VERIFY_FAILED error is raised. It's often not that the server's certificate is inherently "bad," but rather that your client doesn't have the necessary information (like the correct root CA) to validate it.
Common Causes
In my experience, encountering this error usually boils down to a few typical scenarios:
- Outdated CA Certificates on the Client: Your system or Python environment might have an old bundle of trusted Certificate Authorities. If the server's certificate was issued by a relatively new CA, or an intermediate CA that relies on a newer root, your client might not recognize it.
- Self-Signed Certificates: Common in internal development, testing environments, or for small services where obtaining a publicly trusted certificate isn't practical. A self-signed certificate isn't issued by a recognized CA, so clients won't trust it by default.
- Corporate Proxies or Firewalls: Many corporate networks employ SSL interception. This means the proxy acts as a MITM, decrypting and re-encrypting SSL traffic. It presents its own certificate (signed by the corporate root CA) to your client, instead of the server's original certificate. If your client doesn't trust the corporate root CA, verification fails. I've seen this in production when deploying services behind network appliances.
- Incorrect Hostname or IP Address: The certificate is issued for a specific domain name (e.g.,
api.example.com). If your client tries to connect using an IP address or a different hostname (e.g.,dev.api.example.comwhen the cert is forapi.example.com), the hostname verification fails. - Expired or Revoked Server Certificates: Less common, as this is a server-side operational issue. If a server certificate has expired or been revoked, clients will correctly reject it.
- Missing Intermediate Certificates: Sometimes, a server is configured with only its leaf certificate, but not the full chain of intermediate certificates back to a trusted root. Clients might not be able to build the full trust path.
- Python
certifiIssues: Thecertifipackage provides a curated list of trusted root certificates for Python applications. Ifcertifiis missing, outdated, or if another library is overriding its usage incorrectly, you might hit this error.
Step-by-Step Fix
Solving this error requires a systematic approach. Don't immediately jump to disabling verification, as that compromises security.
1. Identify the Target and Environment
First, pinpoint what you're connecting to and where your Python application is running.
* Is it a public API or an internal service?
* Is it running on your local machine, a Docker container, a VM, or a cloud function?
2. Check the Server's Certificate Externally
Use openssl or your browser to inspect the certificate of the target server. This helps determine if the server's certificate itself is the issue (expired, self-signed) or if it's a client-side trust store problem.
# Replace example.com with your target hostname and 443 with the port
openssl s_client -showcerts -verify 5 -connect example.com:443 < /dev/null
Look for:
* Verify return code: 0 (ok): This means openssl on your system trusts the certificate. If your Python still fails, it's likely a Python-specific trust store issue.
* Verification error: ... or a non-zero return code: openssl itself doesn't trust it. Check the error message for clues (e.g., self-signed, expired).
* The Issuer and Subject fields: Who issued the certificate? Does it match your expectations?
* Not Before and Not After dates: Is it expired?
* Subject Alternative Name (SANs): Does the hostname you're connecting to exist in this list?
3. Ensure Your System's CA Certificates are Up-to-Date
Your Python environment often relies on the system's CA certificates or certifi.
- Linux (Debian/Ubuntu):
bash sudo apt-get update sudo apt-get install ca-certificates sudo update-ca-certificates # Rebuilds the /etc/ssl/certs symlinks - Linux (CentOS/RHEL):
bash sudo yum update sudo yum install ca-certificates sudo update-ca-certificates - macOS: CA certificates are managed by the Keychain Access utility and system updates. Python on macOS often uses its own
certifibundle. - Windows: CA certificates are managed via Windows Update. Ensure your system is up-to-date.
4. Update and Verify certifi in Your Python Environment
The certifi package is the most common source of trusted CAs for Python applications across platforms.
pip install --upgrade certifi
You can check which CA bundle requests (which typically uses certifi) is configured to use:
import requests
print(requests.certs.where())
This path should point to a file within your Python environment.
5. Provide a Custom CA Bundle (for Self-Signed or Corporate CAs)
If you're connecting to a server with a self-signed certificate, or through a corporate proxy that uses its own CA, you need to tell your client to trust that specific CA.
- Obtain the CA certificate: Get the
.pemor.crtfile for the self-signed certificate or the corporate root CA. Your IT department or the service administrator should provide this. -
Specify the CA path in your code:
```python
import requests
import osPath to your custom CA certificate file (e.g., my_corporate_root.pem)
Ensure this file is accessible by your Python application.
custom_ca_path = os.path.join(os.path.dirname(file), 'my_corporate_root.pem')
Or an absolute path: custom_ca_path = '/etc/ssl/certs/my_corporate_root.pem'
try:
response = requests.get('https://my-internal-service.example.com', verify=custom_ca_path)
response.raise_for_status()
print("Successfully connected with custom CA.")
except requests.exceptions.SSLError as e:
print(f"SSL Error with custom CA: {e}")
```
This approach is much safer than disabling verification entirely.
6. Add Custom CA to System-Wide Trust Store (Advanced)
For consistent behavior across many applications on a system (e.g., a Docker image), you might add your custom CA to the operating system's trust store.
- Linux:
bash sudo cp my_corporate_root.pem /usr/local/share/ca-certificates/my_corporate_root.crt sudo update-ca-certificates
(Note:update-ca-certificatesexpects.crtextensions for files in/usr/local/share/ca-certificates/).
7. Temporarily Disable Verification (Use with Extreme Caution!)
Only use verify=False for development, testing, or isolated internal networks where the risks are fully understood and accepted. Never deploy code to production with verify=False when connecting to external services, as it completely bypasses the security benefits of SSL/TLS.
import requests
import urllib3
# Suppress the InsecureRequestWarning that requests will emit
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
try:
response = requests.get('https://self-signed-dev-server.local', verify=False)
response.raise_for_status()
print("Connected to self-signed server (verification disabled).")
except requests.exceptions.SSLError as e:
print(f"Still got an SSL Error: {e}") # Should not happen unless there are other SSL issues
except requests.exceptions.RequestException as e:
print(f"Other Request Error: {e}")
8. Check Hostname Match
Confirm that the hostname you are using in your code exactly matches one of the Subject Alternative Names (SANs) in the server's certificate. If you're connecting via an IP address, and the certificate only lists hostnames, verification will fail.
Code Examples
Here are concise, copy-paste-ready examples covering common scenarios with the popular requests library and a lower-level ssl example.
Using requests with Default Verification
This is the standard, secure way to make requests. It relies on certifi or the system's CA bundle.
import requests
target_url = 'https://www.google.com' # A well-known, trusted site
# target_url = 'https://some-api.example.com' # Your actual target
try:
response = requests.get(target_url, timeout=10)
response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx)
print(f"Successfully connected to {target_url}.")
print(f"Status Code: {response.status_code}")
print(f"Response Body (first 100 chars): {response.text[:100]}...")
except requests.exceptions.SSLError as e:
print(f"SSL Certificate Verification Failed for {target_url}: {e}")
except requests.exceptions.ConnectionError as e:
print(f"Connection Error for {target_url}: {e}")
except requests.exceptions.Timeout as e:
print(f"Timeout Error for {target_url}: {e}")
except requests.exceptions.RequestException as e:
print(f"An unexpected Requests error occurred for {target_url}: {e}")
Using requests with a Custom CA Bundle
When dealing with self-signed certificates or corporate proxies.
import requests
import os
# Assume 'my_corporate_ca.pem' is in the same directory as this script,
# or provide an absolute path to your custom CA certificate.
custom_ca_file = os.path.join(os.path.dirname(__file__), 'my_corporate_ca.pem')
# Example: target_url = 'https://internal-dev-app.mycorp.local'
target_url = 'https://some-internal-service.example.com'
if not os.path.exists(custom_ca_file):
print(f"Error: Custom CA file not found at {custom_ca_file}")
print("Please ensure 'my_corporate_ca.pem' exists or provide the correct path.")
else:
try:
# requests will use the specified CA bundle to verify the server's certificate
response = requests.get(target_url, verify=custom_ca_file, timeout=10)
response.raise_for_status()
print(f"Successfully connected to {target_url} using custom CA.")
print(f"Status Code: {response.status_code}")
print(f"Response Body (first 100 chars): {response.text[:100]}...")
except requests.exceptions.SSLError as e:
print(f"SSL Certificate Verification Failed for {target_url} with custom CA: {e}")
except requests.exceptions.RequestException as e:
print(f"An unexpected Requests error occurred with custom CA: {e}")
Using requests with Verification Disabled (DANGER!)
import requests
import urllib3
# IMPORTANT: Suppress InsecureRequestWarning only if you understand and accept the risks.
# This prevents a flood of warnings when verify=False is used.
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
target_url = 'https://a-dev-server-with-self-signed-cert.local'
try:
# Setting verify=False bypasses certificate verification.
# NEVER do this in production or when connecting to untrusted networks/servers.
response = requests.get(target_url, verify=False, timeout=10)
response.raise_for_status()
print(f"Successfully connected to {target_url} (SSL verification disabled).")
print(f"Status Code: {response.status_code}")
print(f"Response Body (first 100 chars): {response.text[:100]}...")
except requests.exceptions.SSLError as e:
print(f"Unexpected SSL Error even with verification disabled for {target_url}: {e}")
except requests.exceptions.RequestException as e:
print(f"An unexpected Requests error occurred with verification disabled for {target_url}: {e}")
Lower-level ssl Module Example
For direct socket programming or understanding how requests works under the hood.
import ssl
import socket
hostname = 'www.google.com' # Or your target hostname, e.g., 'internal-api.mycorp.com'
port = 443
try:
# Create a default SSL context, which typically loads system-wide CAs or certifi
context = ssl.create_default_context()
# --- Optional: Customizations for the context ---
# To load a specific CA file:
# context.load_verify_locations(cafile="/path/to/my_corporate_root.pem")
# To explicitly specify a certificate for client authentication (if server requires it):
# context.load_cert_chain(certfile="/path/to/client_cert.pem", keyfile="/path/to/client_key.pem")
# To disable hostname verification (DANGER!):
# context.check_hostname = False
# To disable all certificate verification (EXTREME DANGER!):
# context.verify_mode = ssl.CERT_NONE
with socket.create_connection((hostname, port), timeout=10) as sock:
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
print(f"SSL handshake successful for {hostname}.")
print(f"Cipher used: {ssock.cipher()}")
peer_cert = ssock.getpeercert()
# print("Server Certificate Details:")
# for key, value in peer_cert.items():
# print(f" {key}: {value}")
except ssl.SSLError as e:
print(f"SSL Error during low-level connection to {hostname}:{port}: {e}")
except socket.timeout:
print(f"Connection to {hostname}:{port} timed out.")
except ConnectionRefusedError:
print(f"Connection refused to {hostname}:{port}. Is the server running?")
except Exception as e:
print(f"An unexpected error occurred: {e}")
Environment-Specific Notes
The context in which your Python application runs significantly impacts how you troubleshoot and resolve CERTIFICATE_VERIFY_FAILED.
Cloud Environments (AWS Lambda, GCP Cloud Functions, Azure Functions, Kubernetes)
- Generally Up-to-Date CAs: Most managed cloud runtimes and base images (like those for Lambda or Kubernetes) come with reasonably current CA certificate bundles.
- Internal Services & VPCs: The error often arises when connecting to internal services, databases, or APIs within your cloud provider's private network (e.g., a private AWS RDS instance, a service mesh in Kubernetes). These might use self-signed certificates, private CAs, or internal domain names that don't match public certificates.
- Kubernetes Specifics:
- Docker Images: Ensure your Dockerfile includes
ca-certificates(see Docker section below). - Mounting CAs: If you need a custom corporate CA, you'll need to mount it into your container using a
ConfigMapand then configure your application to use it, or add it to the container's/usr/local/share/ca-certificates/and runupdate-ca-certificates. - Service Mesh (e.g., Istio): Service meshes often handle mTLS (mutual TLS) between services, which can introduce its own set of certificates. Understand your mesh's certificate management.
- Docker Images: Ensure your Dockerfile includes
Docker Containers
Docker containers are isolated, so their trust store is determined by their base image and Dockerfile.
- Base Image Choice: Lightweight base images (like Alpine) may have minimal packages, sometimes lacking
ca-certificates. Debian/Ubuntu-based images usually include them. -
Dockerfile Best Practices:
```dockerfile
# Example for Debian/Ubuntu based image
FROM python:3.9-slim-busterEnsure ca-certificates are installed and up-to-date
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && rm -rf /var/lib/apt/lists/*
If you need to add a corporate/custom CA:
COPY my_corporate_root.pem /usr/local/share/ca-certificates/my_corporate_root.crt
RUN update-ca-certificates
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .CMD ["python", "app.py"]
`` For Alpine-based images, useapk add --no-cache ca-certificates`.
Local Development
This is where I most frequently encounter CERTIFICATE_VERIFY_FAILED.
- Self-Signed Development Servers: Easy to set up HTTPS locally but browsers complain, and Python clients will too. You'll likely need to use a custom CA bundle or
verify=False(temporarily). - Corporate VPN/Proxies: If you're working from home or a coffee shop, your corporate VPN might route traffic through a proxy that performs SSL interception. When you connect directly at the office, it might work, but through the VPN, it breaks. Obtain the corporate CA cert.
pyenv/condaenvironments: Each Python environment might have its owncertifipackage. Ensurecertifiis installed and up-to-date within that specific environment.
Frequently Asked Questions
Q: Is it safe to use verify=False?
A: No, generally it is not safe. Using verify=False (or ssl.CERT_NONE) disables the critical security check that ensures you are talking to the legitimate server. This leaves your connection vulnerable to Man-in-the-Middle (MITM) attacks where an attacker can intercept, read, and modify your data without your application knowing. Only use it in tightly controlled development or testing environments where you fully understand and accept the risks.
Q: Why does it work in my browser but not in Python?
A: Browsers often have more extensive and frequently updated trust stores, and critically, they allow you to manually accept or "trust" untrusted certificates (like self-signed ones) for specific sites, storing that exception locally. Python, by default (especially when using certifi), relies on its own curated set of trusted root CAs and does not offer such interactive prompts. If your browser trusts a certificate, but Python doesn't, it usually means you've either manually trusted it in the browser, or the certificate is issued by a CA that Python's certifi or your system's trust store doesn't recognize.
Q: My company uses a proxy that intercepts SSL. How do I fix this?
A: This is a common corporate environment issue. You need to obtain your corporate proxy's root CA certificate (usually a .pem or .crt file) from your IT department. Then, configure your Python application to trust this specific CA. The safest way is to pass the path to this CA file to your requests calls via the verify='path/to/corporate_ca.pem' parameter. Alternatively, you can add the corporate CA to your system's trust store (e.g., /usr/local/share/ca-certificates/ on Linux) so all applications on that system can use it. Remember to also set HTTP_PROXY and HTTPS_PROXY environment variables if the proxy requires them.
Q: What is certifi and why is it important?
A: certifi is a Python package that provides a reliable, curated collection of trusted root Certificate Authority certificates. Many Python libraries, including requests, use certifi as their default trust store. Its importance lies in providing a consistent and up-to-date set of CAs across different operating systems, which can otherwise vary significantly in their native certificate bundles. It helps ensure that your Python applications can securely connect to the vast majority of public HTTPS endpoints.
Q: How do I check what CA bundle requests is using?
A: You can easily check the path to the CA bundle that requests is configured to use with a simple Python command:
import requests
print(requests.certs.where())
This will typically output the path to the certifi bundle within your active Python environment.