DevOpsil

SSL Certificate Verification Failed In Curl And Python Requests: Diagnosing And Fixing CERTIFICATE_VERIFY_FAILED Errors

Aareez AsifAareez Asif5 min read

SSL Certificate Verification Failed: Quick Reference for curl and Python requests

SSL verification errors are one of those issues that bite you at the worst possible moment — usually in production, usually when you're on-call. Here's your no-nonsense reference for diagnosing and fixing CERTIFICATE_VERIFY_FAILED errors fast.


Quick Diagnosis Checklist

Before touching any code, run through this:

  • Is the certificate expired?
  • Is the hostname mismatched?
  • Is the CA chain incomplete (missing intermediate certs)?
  • Is the system CA bundle outdated?
  • Are you behind a corporate proxy doing SSL inspection?

Diagnose with curl First

curl is your fastest diagnostic tool. Always start here.

# Basic check — verbose output shows the full handshake
curl -vvv https://your-endpoint.com

# Show certificate details without downloading content
curl -vvv --head https://your-endpoint.com 2>&1 | grep -A5 "Server certificate"

# Test with explicit CA bundle
curl --cacert /path/to/ca-bundle.crt https://your-endpoint.com

# Check what cert the server is actually presenting
openssl s_client -connect your-endpoint.com:443 -showcerts 2>/dev/null | openssl x509 -noout -text | grep -E "Subject:|Issuer:|Not After"

# Test a specific SNI (critical for multi-tenant endpoints)
openssl s_client -connect your-endpoint.com:443 -servername your-endpoint.com

Common Error Messages Decoded

ErrorLikely Cause
CERTIFICATE_VERIFY_FAILEDCA not trusted or chain broken
SSL: CERTIFICATE_VERIFY_FAILEDPython urllib/requests CA mismatch
curl: (60) SSL certificate problemMissing or untrusted CA
certificate has expiredObvious — cert rotation needed
unable to get local issuer certificateMissing intermediate certificate
hostname mismatchSAN/CN doesn't match requested host

Python requests Fixes

The Right Way — Provide the CA Bundle

import requests

# Point to your CA bundle (preferred approach)
response = requests.get(
    "https://your-endpoint.com",
    verify="/path/to/ca-bundle.crt"
)

# For corporate/internal CAs — bundle them yourself
response = requests.get(
    "https://internal-api.corp.com",
    verify="/etc/ssl/certs/corporate-ca.pem"
)

Find Where Python Is Looking for CAs

import ssl
import certifi

# What CA bundle is Python currently using?
print(ssl.get_default_verify_paths())

# Where certifi stores its bundle
print(certifi.where())

# Check the actual SSL context
ctx = ssl.create_default_context()
print(ctx.get_ca_certs())

Fix for macOS (Python 3.6+)

macOS Python installations famously don't use the system keychain. Run this once:

# Usually found in your Python install directory
/Applications/Python\ 3.x/Install\ Certificates.command

# Or install certifi and point to it
pip install certifi
import ssl
import certifi

# Override the default SSL context
ssl_context = ssl.create_default_context(cafile=certifi.where())

Append a Custom CA to certifi's Bundle

import certifi
import shutil
import os

# One-time setup: append your corporate CA
custom_ca_path = "/tmp/combined-ca-bundle.pem"
shutil.copy(certifi.where(), custom_ca_path)

with open(custom_ca_path, "ab") as outfile:
    with open("/path/to/corporate-ca.pem", "rb") as infile:
        outfile.write(infile.read())

# Use the combined bundle
response = requests.get("https://internal-api.corp.com", verify=custom_ca_path)

Environment Variable Shortcuts

These work globally without code changes — useful for quick debugging:

# Point requests/urllib to a specific CA bundle
export REQUESTS_CA_BUNDLE=/path/to/ca-bundle.crt
export SSL_CERT_FILE=/path/to/ca-bundle.crt

# For curl
export CURL_CA_BUNDLE=/path/to/ca-bundle.crt

Corporate Proxy / SSL Inspection Fix

If you're behind a proxy doing SSL inspection (very common in enterprise environments), you need the proxy's CA cert in your trust store.

# Linux — add the corporate CA system-wide
sudo cp corporate-ca.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates                          # Debian/Ubuntu
sudo update-ca-trust                                 # RHEL/CentOS

# Verify it's in the system bundle now
grep "Corporate CA" /etc/ssl/certs/ca-certificates.crt
# Python — combine system CAs with proxy CA
import requests

session = requests.Session()
session.verify = "/etc/ssl/certs/ca-certificates.crt"  # Updated system bundle
response = session.get("https://external-api.com")

Kubernetes-Specific: Mounting CA Certs into Pods

# Mount a corporate CA as a ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
  name: corporate-ca
data:
  ca.crt: |
    -----BEGIN CERTIFICATE-----
    <your-ca-cert-here>
    -----END CERTIFICATE-----
---
# In your Pod spec
volumes:
  - name: ca-bundle
    configMap:
      name: corporate-ca

containers:
  - name: app
    env:
      - name: REQUESTS_CA_BUNDLE
        value: /etc/ssl/custom/ca.crt
    volumeMounts:
      - name: ca-bundle
        mountPath: /etc/ssl/custom

The Nuclear Option (Don't Do This in Production)

I'm including this only so you know what it looks like — and can recognize it in code reviews and reject it.

# DO NOT USE IN PRODUCTION
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
response = requests.get("https://your-endpoint.com", verify=False)  # ❌ Never

# curl equivalent — also never in production
curl -k https://your-endpoint.com  # ❌ -k/--insecure disables all verification

verify=False silences the error by removing all protection. You're not fixing the problem — you're just agreeing to be MITM'd quietly.


Quick Decision Tree

SSL error hit?
├── Run openssl s_client → cert expired? → Rotate the cert
├── Hostname mismatch? → Fix the SAN on the cert or use correct hostname
├── "unable to get local issuer" → Missing intermediate → get full chain from server owner
├── Corporate proxy? → Add proxy CA to system trust store
└── macOS Python? → Run Install Certificates.command or use certifi

Bottom line: Always fix the root cause. The correct CA bundle should be in your trust store, your containers should mount custom CAs via ConfigMaps, and verify=False should never survive a code review. Take the extra 10 minutes to do it right.

Share:

Was this article helpful?

Aareez Asif
Aareez Asif

Senior Kubernetes Architect

10+ years orchestrating containers in production. Battle-tested opinions on everything from pod scheduling to service mesh. I've seen clusters burn and helped rebuild them better.

Related Articles

More in Security

View all →

Discussion