When using the Python requests library to interact with an HTTPS endpoint, you may encounter an ssl.SSLCertVerificationError, often presented as requests.exceptions.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed.
import requests
try:
# Example: connecting to a service with a self-signed certificate
response = requests.get('https://internal.service.local')
print(response.status_code)
except requests.exceptions.SSLError as e:
print(e)
Output: HTTPSConnectionPool(...): Max retries exceeded with url: / (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate')))
This error is not a bug; it is a critical security feature. It means that Python and the requests library successfully detected a problem with the server's SSL certificate and aborted the connection to protect you.
This typically happens for one of three reasons:
1. Self-signed certificate: The server is using a certificate that it generated itself (a "self-signed" certificate), which is not trusted by any public Certificate Authority (CA). This is common in development or internal environments.
2. Corporate/proxy certificate: You are in a corporate environment that inspects (decrypts and re-encrypts) HTTPS traffic using its own internal root CA, which your Python environment does not trust.
3. Invalid public certificate: The server's certificate is genuinely invalid (it's expired, it doesn't match the domain name, or it's issued by an untrusted or revoked CA).
A widely suggested "solution" on forums is to simply disable SSL verification.
# WARNING: HIGHLY INSECURE. DO NOT USE IN PRODUCTION.
response = requests.get('https://internal.service.local', verify=False)
While this makes the error disappear, it is extremely dangerous.
Setting verify=False instructs requests to not validate the server's certificate at all. This makes your application completely vulnerable to Man-in-the-Middle (MITM) attacks. An attacker on the same network can intercept the connection, present a fraudulent certificate, and then read, log, or even modify all data (like passwords, API keys, or personal user data) sent between your application and the server.
This "fix" is only acceptable for temporary debugging on a fully isolated, trusted local machine and should never be committed to a codebase.
Here are the secure and correct ways to resolve this error, depending on your scenario.
If you are communicating with a server you trust that uses a self-signed certificate (e.g., an internal development server), the correct solution is to explicitly tell requests to trust that specific certificate.
1. Obtain the public part of the server's certificate (a .pem or .crt file) from the server administrator.
2. Save this file in your project or on your machine.
3. Pass the path to this certificate file to the verify parameter.
# The correct, secure way for self-signed certificates
cert_path = '/path/to/my-internal-server.pem'
try:
response = requests.get('https://internal.service.local', verify=cert_path)
print("Connection successful and verified.")
except requests.exceptions.SSLError as e:
print(f"Error: {e}")
Now, your connection is still encrypted, but requests will validate the connection only against the certificate you provided. If an attacker tries a MITM attack, their certificate will not match, and requests will correctly raise an error.
If you are in a corporate environment, your IT department likely has a root CA certificate that you must trust. The requests library uses the certifi package by default for its bundle of trusted CAs. You can tell requests to use your company's bundle in two main ways:
This is the cleanest method. Point this environment variable to the location of your company's CA bundle file. requests will automatically detect and use it.
# In your .bashrc, .zshrc, or CI/CD environment
xinit@localhost:~$ export REQUESTS_CA_BUNDLE=/path/to/corporate-ca-bundle.pem
Your Python code then requires no changes:
# This will now automatically use the bundle from the environment variable
response = requests.get('https://internal.service.local')
You can programmatically add your corporate CA to the list of CAs that certifi trusts. This is more complex and may need to be re-applied when certifi is updated.
If you see this error when trying to connect to a legitimate, public website (like google.com or github.com), do not use verify=False. This indicates a problem with your environment or network.
1. Your CA bundle is outdated: The certifi package, which requests relies on, may be old. The fix is simple:
xinit@localhost:~$ pip install --upgrade certifi
2. You are being intercepted: You might be on a public Wi-Fi network with a "captive portal" or in an environment that is actively intercepting your traffic. Do not send sensitive data.
3. The server is misconfigured: The public website itself might have an expired or misconfigured SSL certificate. You cannot fix this; the site owner must.
The CERTIFICATE_VERIFY_FAILED error is a vital security mechanism. Bypassing it with verify=False is a dangerous anti-pattern that should be avoided. The professional approach is to correctly manage trust by providing a specific certificate via the verify parameter or by configuring your environment to trust a custom Certificate Authority.