When working with Python’s requests
library to interact with HTTPS services, particularly within corporate environments, developers frequently encounter the dreaded requests.exceptions.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed
. This error signals that Python, through its underlying SSL/TLS mechanisms, could not validate the SSL certificate presented by the server. This typically occurs when the server uses a certificate issued by a custom enterprise Certificate Authority (CA) that isn’t part of the public CAs requests
trusts by default.
The requests
library, for security, relies on the certifi
package, which provides Mozilla’s collection of trusted root CAs. If your organization’s internal CA isn’t in this public set, or if you’re behind an SSL-inspecting corporate proxy that re-signs certificates, verification will fail. This article provides a comprehensive guide to understanding, troubleshooting, and correctly resolving this common SSL error, ensuring secure and successful connections.
Understanding the CERTIFICATE_VERIFY_FAILED
Error
At its core, SSL/TLS (Secure Sockets Layer/Transport Layer Security) ensures that communication between a client (your Python script) and a server is encrypted and that the server is authentic. This authenticity is established through digital certificates issued by Certificate Authorities (CAs). Your system maintains a list of trusted root CAs. If a server’s certificate is signed by a CA (or an intermediate CA that chains up to a root CA) not in this trusted list, the CERTIFICATE_VERIFY_FAILED
error occurs.
Enterprise networks often use their own internal CAs to issue certificates for internal servers and services. These internal CAs are trusted within the enterprise but not globally. Similarly, SSL-inspecting proxies decrypt, inspect, and then re-encrypt traffic using a certificate signed by the proxy’s own CA, which clients must also trust.
Core Solutions for Trusting Enterprise CAs
The primary goal is to inform requests
about your enterprise CA so it can be trusted. Here are the recommended methods:
1. The verify
Parameter in requests
Calls
The most direct way to specify a custom CA for a particular request or set of requests is by using the verify
parameter. You provide the path to your CA certificate file (or a bundle of CA certificates) in PEM format.
|
|
This method offers fine-grained control, specifying the CA on a per-request basis.
2. The REQUESTS_CA_BUNDLE
Environment Variable
For a more global approach within your application’s environment, requests
automatically honors the REQUESTS_CA_BUNDLE
environment variable. If this variable is set to the path of your CA bundle file, requests
will use it for all SSL verifications by default.
First, set the environment variable in your shell:
|
|
Then, your Python code can be simpler:
|
|
requests
also checks for CURL_CA_BUNDLE
as a fallback if REQUESTS_CA_BUNDLE
is not defined.
3. Using requests.Session
Objects
If your application makes multiple requests to the same host(s) or requires persistent parameters (like custom CA verification), using a requests.Session
object is more efficient. You can set the CA bundle path on the session’s verify
attribute.
|
|
Preparing Your CA Bundle File
The CA certificate(s) must be in PEM (Privacy Enhanced Mail) format. A PEM file is a Base64 encoded text file with -----BEGIN CERTIFICATE-----
and -----END CERTIFICATE-----
markers.
- Single CA Certificate: If your enterprise uses a single root CA that directly signs server certificates, this file will contain just that root CA’s certificate.
- CA Bundle (Root + Intermediates): If your server certificate is signed by an intermediate CA, the bundle should contain the intermediate CA’s certificate and its issuing root CA’s certificate. The order is typically the signing CA followed by its issuer, up to the root. However, for a trust bundle, including just the enterprise root CA is often sufficient if servers provide necessary intermediates. Your IT/Security department should provide the correct CA certificate(s).
You can concatenate multiple PEM-formatted certificates into a single file:
|
|
Ensure this bundle file is readable by your Python application.
System-Wide Trust (Platform Dependent)
An alternative is to install the enterprise CA certificate into your operating system’s trust store. This makes the CA trusted by many applications on your system, not just Python requests
.
The method for adding a CA to the system trust store varies by OS:
- Linux (Debian/Ubuntu): Copy PEM to
/usr/local/share/ca-certificates/
and runsudo update-ca-certificates
. (Seeman update-ca-certificates
). - Linux (RHEL/CentOS): Copy PEM to
/etc/pki/ca-trust/source/anchors/
and runsudo update-ca-trust extract
. (Seeman update-ca-trust
). - macOS: Use Keychain Access utility to import the certificate into the System keychain and mark it as trusted.
- Windows: Use Certificate Manager (
certmgr.msc
) to import into “Trusted Root Certification Authorities”.
After installing the CA system-wide, the pip-system-certs
package can patch requests
(and pip
itself) to use the OS trust store.
|
|
Once installed, requests
may automatically pick up CAs from the system store. However, this approach has broader implications and often requires administrative privileges. Using verify
or REQUESTS_CA_BUNDLE
provides more explicit, application-level control.
Diagnosing SSL Issues
If you’re still facing problems, these diagnostic steps can help:
1. openssl s_client
The OpenSSL command-line tool is invaluable for testing SSL/TLS connections and inspecting certificates directly, independent of Python.
To check connectivity and verify against your CA bundle using openssl s_client
:
|
|
Look for Verify return code: 0 (ok)
. Any other code indicates a problem.
To view the certificate chain presented by the server:
|
|
This helps you see exactly which certificates the server is sending, which can be useful for assembling your CA bundle.
2. Python http.client
Debugging
For low-level insight into the SSL handshake process within Python, you can enable debugging for http.client
(used by urllib3
, which requests
uses).
|
|
This will print verbose information about the SSL handshake, including certificate details and potential error messages from the OpenSSL library.
3. Inspecting Certificate Contents with openssl x509
If you have a certificate file (e.g., my_ca.crt
or my_ca.pem
) and want to inspect its contents or convert its format using openssl x509
:
To view details of a PEM-encoded certificate:
|
|
To convert a certificate from DER format to PEM format:
|
|
Common Pitfalls and Anti-Patterns
- NEVER use
verify=False
in Production: Disabling SSL verification (requests.get(url, verify=False)
) silences the error but exposes your application to serious security risks, including Man-in-the-Middle (MITM) attacks. Only use this for temporary, isolated local testing with full awareness of the implications. - Incorrect Path: Double-check the path to your CA bundle file. Typos are common.
- Permissions: Ensure your application has read permissions for the CA bundle file.
- Incorrect Bundle Content:
- Using the server’s certificate instead of the CA’s certificate.
- Missing intermediate CA(s) in the bundle. The bundle needs the certificate(s) that your system doesn’t already trust but are needed to complete the chain to a trusted root.
- File not in valid PEM format.
- Environment Variable Not Set/Exported: If using
REQUESTS_CA_BUNDLE
, ensure it’s correctly set and exported in the environment where your script runs. Changes to.bashrc
or similar might require a new terminal session or sourcing the file. - Outdated CA Bundle: Ensure your custom CA bundle or the
certifi
package is reasonably up-to-date, although for enterprise CAs, the issue is usually trust, not expiry of the CA itself.
Advanced: Custom SSLContext
with requests.adapters.HTTPAdapter
For scenarios requiring fine-grained control over SSL/TLS parameters (like specific TLS versions, cipher suites, or other options not exposed directly by requests
), you can create a custom ssl.SSLContext
and use it with requests
via an HTTPAdapter
.
|
|
This is an advanced technique and typically not required for standard enterprise CA issues but offers maximum flexibility.
Considerations for Containerized Environments (Docker/Kubernetes)
When running Python applications in containers:
- Mount CA Bundle: The preferred method is to mount your custom CA bundle file into the container (e.g., using Docker volumes) and then use
REQUESTS_CA_BUNDLE
or theverify
parameter to point to its path within the container. - Add to Container’s System Trust Store: Alternatively, during the Docker image build process (
Dockerfile
), you can copy the CA certificate into the container’s system trust store (e.g.,/usr/local/share/ca-certificates/
on Debian/Ubuntu, then runupdate-ca-certificates
). If you use this method, also installpip-system-certs
in your container.
Conclusion
The SSLError: CERTIFICATE_VERIFY_FAILED
error in Python requests
is a common hurdle in enterprise settings but is entirely solvable by correctly informing requests
about your organization’s custom Certificate Authorities. Prioritize using the verify
parameter for specific calls, REQUESTS_CA_BUNDLE
for environment-wide configuration, or Session.verify
for repeated requests. Always ensure your CA bundle is correctly formatted (PEM) and contains the necessary certificates.
Resist the dangerous temptation to disable SSL verification (verify=False
). Instead, invest the effort in proper CA management and diagnostic techniques like openssl s_client
. By understanding the underlying SSL/TLS trust mechanisms and applying these solutions, you can build secure, reliable Python applications that seamlessly integrate with internal HTTPS services.