SSL certificate verification is a critical security feature in Python's Requests library that protects against man-in-the-middle attacks. This guide covers how to manage SSL certificate verification for different scenarios.
Default SSL Verification
By default, Requests automatically verifies SSL certificates for HTTPS requests using the system's trusted Certificate Authority (CA) bundle:
import requests
try:
response = requests.get('https://httpbin.org/get')
print(f"Status: {response.status_code}")
print("SSL certificate verified successfully")
except requests.exceptions.SSLError as e:
print(f"SSL verification failed: {e}")
When SSL verification fails, Requests raises a requests.exceptions.SSLError
.
Checking Certificate Details
You can inspect SSL certificate information in your responses:
import requests
response = requests.get('https://httpbin.org/get')
# Access SSL info through the underlying urllib3 response
if hasattr(response.raw, '_original_response'):
ssl_info = response.raw._original_response.peer_cert
print(f"Certificate subject: {ssl_info.get('subject', 'N/A')}")
Disabling SSL Verification (Not Recommended)
⚠️ Security Warning: Only disable SSL verification in development or when you fully understand the security implications.
import requests
import urllib3
# Disable SSL warnings when verification is disabled
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
response = requests.get('https://self-signed.badssl.com/', verify=False)
print(f"Status: {response.status_code}")
Session-Level SSL Configuration
Configure SSL settings for all requests in a session:
import requests
session = requests.Session()
session.verify = False # Apply to all requests in this session
response = session.get('https://example.com')
Using Custom CA Bundles
For corporate environments or custom certificates, specify a custom CA bundle file:
import requests
# Using a custom CA bundle file
response = requests.get(
'https://example.com',
verify='/path/to/custom-ca-bundle.pem'
)
# Using certifi's CA bundle explicitly
import certifi
response = requests.get(
'https://example.com',
verify=certifi.where()
)
Creating a Custom CA Bundle
import requests
import ssl
import os
# Get the default CA bundle location
ca_bundle_path = requests.certs.where()
print(f"Default CA bundle: {ca_bundle_path}")
# Combine default bundle with custom certificate
custom_ca_path = '/tmp/custom-ca-bundle.pem'
with open(custom_ca_path, 'w') as custom_bundle:
# Copy default certificates
with open(ca_bundle_path, 'r') as default_bundle:
custom_bundle.write(default_bundle.read())
# Add your custom certificate
with open('/path/to/your-custom-cert.pem', 'r') as custom_cert:
custom_bundle.write('\n')
custom_bundle.write(custom_cert.read())
# Use the custom bundle
response = requests.get('https://your-custom-site.com', verify=custom_ca_path)
Client Certificate Authentication
For mutual TLS authentication, provide client certificates:
import requests
# Client certificate and private key as separate files
response = requests.get(
'https://client-cert-required.example.com',
cert=('/path/to/client.crt', '/path/to/client.key')
)
# Client certificate with password-protected private key
response = requests.get(
'https://client-cert-required.example.com',
cert=('/path/to/client.crt', '/path/to/client.key', 'key_password')
)
# Combined certificate and key in single file
response = requests.get(
'https://client-cert-required.example.com',
cert='/path/to/client.pem'
)
Client Certificate with Custom CA
import requests
response = requests.get(
'https://mutual-tls.example.com',
cert=('/path/to/client.crt', '/path/to/client.key'),
verify='/path/to/custom-ca.pem'
)
Advanced SSL Configuration
Using SSL Context
For fine-grained SSL control, create a custom SSL context:
import requests
import ssl
from requests.adapters import HTTPAdapter
from urllib3.util.ssl_ import create_urllib3_context
class CustomSSLAdapter(HTTPAdapter):
def init_poolmanager(self, *args, **kwargs):
context = create_urllib3_context()
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
# Add custom SSL settings here
kwargs['ssl_context'] = context
return super().init_poolmanager(*args, **kwargs)
session = requests.Session()
session.mount('https://', CustomSSLAdapter())
response = session.get('https://example.com')
Troubleshooting SSL Errors
Common SSL Error Types
import requests
from requests.exceptions import SSLError, ConnectionError
def make_secure_request(url):
try:
response = requests.get(url, timeout=10)
return response
except SSLError as e:
if "certificate verify failed" in str(e):
print(f"Certificate verification failed for {url}")
print("Possible causes:")
print("- Self-signed certificate")
print("- Expired certificate")
print("- Incorrect system time")
elif "hostname" in str(e):
print(f"Hostname mismatch for {url}")
else:
print(f"SSL Error: {e}")
except ConnectionError as e:
print(f"Connection error: {e}")
return None
# Test with different sites
response = make_secure_request('https://expired.badssl.com/')
Debugging SSL Issues
import requests
import ssl
import socket
def debug_ssl_connection(hostname, port=443):
"""Debug SSL connection details"""
try:
# Create SSL context
context = ssl.create_default_context()
# Connect and get certificate info
with socket.create_connection((hostname, port)) as sock:
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
cert = ssock.getpeercert()
print(f"Certificate for {hostname}:")
print(f" Subject: {cert.get('subject')}")
print(f" Issuer: {cert.get('issuer')}")
print(f" Version: {cert.get('version')}")
print(f" Serial: {cert.get('serialNumber')}")
print(f" Not Before: {cert.get('notBefore')}")
print(f" Not After: {cert.get('notAfter')}")
except Exception as e:
print(f"SSL debug failed: {e}")
# Debug SSL for a specific site
debug_ssl_connection('httpbin.org')
Updating Certificate Bundle
import requests
import certifi
# Check current certifi version and update if needed
print(f"Current certifi version: {certifi.__version__}")
print(f"CA bundle location: {certifi.where()}")
# Force update certificates (requires pip install --upgrade certifi)
response = requests.get('https://httpbin.org/get')
print(f"Request successful: {response.status_code == 200}")
Best Practices
1. Always Use SSL Verification in Production
import os
import requests
# Environment-based SSL configuration
VERIFY_SSL = os.getenv('VERIFY_SSL', 'true').lower() == 'true'
response = requests.get('https://api.example.com', verify=VERIFY_SSL)
2. Handle SSL Errors Gracefully
import requests
from requests.exceptions import SSLError
import time
def robust_request(url, max_retries=3):
for attempt in range(max_retries):
try:
response = requests.get(url, timeout=10)
return response
except SSLError as e:
print(f"SSL error on attempt {attempt + 1}: {e}")
if attempt < max_retries - 1:
time.sleep(2 ** attempt) # Exponential backoff
else:
raise
except Exception as e:
print(f"Other error: {e}")
raise
response = robust_request('https://httpbin.org/get')
3. Log SSL Configuration
import requests
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
def logged_request(url, **kwargs):
verify_setting = kwargs.get('verify', True)
cert_setting = kwargs.get('cert', None)
logger.info(f"Making request to {url}")
logger.info(f"SSL verification: {verify_setting}")
logger.info(f"Client certificate: {'Yes' if cert_setting else 'No'}")
return requests.get(url, **kwargs)
response = logged_request('https://httpbin.org/get')
Summary
- Default behavior: SSL verification is enabled and uses system CA bundle
- Custom CA bundles: Use
verify='/path/to/ca-bundle.pem'
for custom certificates - Client certificates: Use
cert=('cert.pem', 'key.pem')
for mutual TLS - Disable verification: Use
verify=False
only in development environments - Troubleshooting: Check certificate validity, system time, and CA bundle updates
Always prioritize security by keeping SSL verification enabled in production environments. Only disable or customize SSL settings when you have a clear understanding of the security implications and requirements.