Skip to content
ssldays

Guide

Python requests SSL certificate expired

Python requests raises SSLError certificate verify failed when the server leaf is expired or untrusted. Reproduce with requests.get, confirm openssl notAfter, renew, and keep verify=True.

Updated May 2, 2026 817 words

python requests certificate verify failed expired is requests surfacing OpenSSL’s rejection of an out-of-date notAfter on the HTTPS host your script calls.

Cron jobs that scrape partner APIs raise requests.exceptions.SSLError at midnight UTC when a forgotten cert expires. Notebooks work until requests.get hits the broken endpoint.

Setting verify=False or REQUESTS_CA_BUNDLE to an outdated PEM trades audit findings for a quick green cell. Renew the remote certificate and keep verify=True in production code.

First response

Paste the full SSLError traceback, Python version, requests and urllib3 versions, and target URL. Run a one-liner requests.get against the same HOST and compare with openssl output. Mention HTTP_PROXY or HTTPS_PROXY if traffic leaves via a proxy.

Do not bump certifi until openssl proves the server leaf is still within its validity window—teams often chase dependencies when the API listener simply expired.

See check SSL expiry in Python for reusable inspection patterns after the incident, and curl SSL certificate expired when shell scripts hit the same host.

What this error means

requests uses urllib3 connection pools and delegates TLS to system or certifi CA bundles. During handshake, the client validates the chain and enforces validity windows.

Messages containing certificate has expired or CERTIFICATE_VERIFY_FAILED with date context mean the server presented an expired link—usually the leaf. Hostname mismatch and missing intermediates produce different substrings but still fail closed with verify=True.

Session mounts and connection pooling reuse TCP but not bypass expiry checks on each new TLS session.

Common causes

Vendor API cert lapsed. Billing integrations and webhooks die while your Django site cert remains valid.

Pinned verify path to stale bundle. Ops dropped a corporate CA file in repo years ago; its intermediate expired.

Kubernetes egress through ingress with old cert. In-cluster jobs call public-hostnames that still serve expired material on one replica.

Legacy TLS termination on load balancer. Renewal updated origin but not the public listener certificate.

certifi upgrade unrelated. Teams blame Python deps when openssl already shows expired notAfter—fix the server first.

Airflow or Celery workers on old VPC endpoints. Regional endpoints have their own certs—openssl each URL your DAG calls.

Confirm from the outside

requests repro

python -c "import requests; requests.get('https://HOST/', timeout=15)"

Detailed traceback

python -c "import requests; exec(\"try:\\n r=requests.get('https://HOST/',timeout=15)\\n print(r.status_code)\\nexcept requests.exceptions.SSLError as e: print(e)\")"

OpenSSL

echo | openssl s_client -connect HOST:443 -servername HOST 2>/dev/null | openssl x509 -noout -dates

How to read the output

Expired notAfter in openssl confirms server-side renewal. If dates are valid, print the served chain with -showcerts and compare against REQUESTS_CA_BUNDLE. urllib3 may report hostname doesn't match when SANs omit the URL host.

Fix checklist

  1. Run repro with verify=True (default).
  2. Capture openssl dates and serial for the API hostname.
  3. Renew certificate on the listener strangers hit on port 443.
  4. Deploy full chain; reload nginx, ALB, or API gateway.
  5. Re-run Python job; expect HTTP response bodies again.
  6. Remove verify=False and security noqa suppressions added in haste.
  7. Update integration tests to assert TLS success, not only HTTP 200.
  8. Add external TLS monitor on dependency URLs.

Record pip freeze versions in the ticket for audit, but close the incident on openssl proof—not on dependency churn alone.

Python rollouts after python requests certificate verify failed expired should rerun the failing DAG task or cron entry with verify=True from the same VPC subnet production uses. If you inject custom CA bundles through REQUESTS_CA_BUNDLE, version-control the PEM and replace intermediates when the issuer rotates—stale bundle files mimic server expiry in logs. Keep a minimal reproduction script in the ops repo linked from the runbook.

Reject myths during triage: pinning an old cert in verify= does not extend notAfter on the wire. Bumping urllib3 without openssl dates wastes time when the API gateway leaf already expired. Split-horizon DNS can make laptops succeed while workers fail—run openssl from the worker node before chasing Python versions.

Platform teams should store dependency URL lists alongside cron schedules so python requests certificate verify failed expired tickets map quickly to owners. After renewal, rerun failing unit tests that patch requests—mocks that disable verify hide regressions until staging hits production trust settings again.

Data engineers exporting pandas or polars frames from HTTPS APIs should use the same verify discipline as microservices—notebooks are production when scheduled. When incidents close, add the dependency hostname to shared monitoring so the next expiry pages owners before batch jobs fail silently overnight.

Close tickets only after the scheduled job that originally failed completes two consecutive successful runs with verify=True—one green manual test is not enough for cron-driven python requests certificate verify failed expired recovery.

When to use automation

Data pipelines depend on third-party HTTPS. Monitor live TLS on each requests base URL your jobs call. Use /check for spot validation; schedule reminders at 30, 14, 7, and 1 days.

Track RDAP on callback domains when legal revalidation gates OV reissue—registration lapses block renewal even when Python code is unchanged.

Frequently asked questions

Custom verify='/path/to/ca.pem'?

Ensure the PEM includes current intermediates. Expired corporate issuing CAs in that file cause verify failures independent of the leaf.

Works in browser, fails in requests?

Browsers cache HSTS and may use different DNS (split horizon). openssl from the same host as Python removes debate.

asyncio/aiohttp too?

Same PKIX rules. Reproduce with requests first; fix the certificate once for all HTTP clients.

Corporate MITM proxy?

Install the proxy root in the bundle Python uses, but never disable verify in production. If MITM re-signs with an expired subordinate CA, renew that internal CA.

See what the server presents right now

Free live certificate check — no signup.