Gunicorn+Gevent工作进程下Django请求出现CERTIFICATE_VERIFY_FAILED错误的原因排查
碰到这种问题确实挺挠头的——明明换sync worker、用runserver、甚至在容器里直接跑shell/console都正常,一切到gevent worker就卡在SSL验证这步。我来帮你拆解下背后的原因,尤其是gevent在SSL处理上的特殊逻辑:
一、Gevent Worker的SSL实现到底特殊在哪?
Gevent为了实现异步IO,并没有直接用Python标准库的ssl模块,而是自己重写了一套gevent.ssl模块替代标准库实现,这就导致了几个关键差异:
- SSL上下文初始化逻辑不同:标准库
ssl.create_default_context()会自动加载系统默认的CA证书池(比如/etc/ssl/certs/下的证书),还会尊重REQUESTS_CA_BUNDLE这类环境变量的配置;但gevent.ssl的上下文初始化逻辑和标准库有差异,可能不会自动合并系统CA池和你指定的自定义证书。 - Monkey Patch的局限性:你试过加
monkey.patch_all(),但这个补丁主要是把阻塞的IO操作换成异步的,并不会改变gevent.ssl本身的证书加载逻辑——也就是说,就算打了补丁,Requests/urllib3在gevent环境下还是会用gevent.ssl处理SSL连接,而非标准库实现。 - Urllib3与Gevent的适配问题:Requests底层依赖urllib3,urllib3在gevent环境下会自动适配
gevent.ssl,但这个适配层可能没把verify参数的配置正确传递给gevent.ssl.SSLContext,导致你指定的证书路径虽然能找到,但验证链时还是缺失issuer信息。
二、结合你的排查结果,具体可能的原因
从你给出的测试结果来看,已经排除了证书本身错误、环境变量直接影响,核心问题大概率出在gevent的SSL上下文未正确加载完整的证书信任链:
- 用sync worker或直接跑shell时,用的是标准库
ssl,它会把你指定的自定义证书和系统默认CA池合并,所以能找到issuer; - 切换到gevent worker后,
gevent.ssl.SSLContext可能只加载了你指定的单张证书,没有自动合并系统根CA,导致验证时无法追溯到issuer(哪怕证书本身是对的); - 你提到
verify=False也没用,这更说明Requests/urllib3在gevent环境下,verify参数的逻辑被gevent.ssl的实现覆盖了,直接跳过验证的命令也没生效。
三、针对性的排查和解决办法
1. 手动构建包含完整信任链的SSL上下文
不要依赖Requests的verify参数,手动创建SSL上下文,把自定义证书和系统CA池合并后传给Requests Session:
import ssl import requests from requests.adapters import HTTPAdapter from django.http import HttpResponse def my_view(request): # 创建默认SSL上下文(自动加载系统CA池) ctx = ssl.create_default_context() # 把自定义CA证书加载到上下文里 ctx.load_verify_locations('/etc/ssl/certs/mycert.cer') # 用这个上下文初始化HTTPAdapter adapter = HTTPAdapter() adapter.init_poolmanager(ssl_context=ctx) # 给Requests Session挂载这个适配器 session = requests.Session() session.mount('https://', adapter) # 用Session发起请求 response = session.get('https://website.domain.com') return HttpResponse('Success')
如果还是不行,试试把自定义证书换成完整的证书链文件(把叶子证书、中间CA、根CA合并成一个.pem文件),再传给load_verify_locations。
2. 强制Gevent使用标准库SSL实现
可以通过环境变量让Gevent fallback到系统标准的ssl模块,绕过它自己的实现,快速验证是不是gevent.ssl的问题:
# 启动Gunicorn前设置环境变量 GEVENT_SSL=ssl gunicorn core.wsgi:application --bind 0.0.0.0:8000 --workers 33 --worker-class gevent --timeout 1200
这个方法相当于放弃Gevent的异步SSL优化,但能快速定位问题根源。如果有效,说明就是Gevent的SSL模块和你的证书环境不兼容。
3. 检查Gevent与Urllib3/Requests的版本兼容性
你的Gevent版本是25.5.1,Requests是2.32.4,部分版本的Urllib3对Gevent的SSL适配有bug,导致证书验证逻辑异常。可以试试升级或降级这几个包的版本,比如把Urllib3单独升级到最新稳定版,再测试。
4. 直接用Gevent的SSL上下文调试
可以在view里加一段调试代码,打印Gevent SSL上下文加载的CA信息,看看是不是和预期一致:
import gevent.ssl as ssl def my_view(request): ctx = ssl.create_default_context() print("Gevent SSL Context CA count:", len(ctx.get_ca_certs())) print("Gevent SSL Context default paths:", ctx.default_verify_paths()) # 后续请求代码...
如果打印的CA列表里没有你的自定义证书,说明确实是上下文没加载到,这时候就需要手动把证书强制加入信任链。
内容来源于stack exchange

