证书过期错误根本不是mongodb服务端问题,而是python运行环境信任的根证书(ca)过旧,无法验证let's encrypt新签发的证书链;必须显式传入tlscafile=certifi.where()才能生效,仅安装certifi不解决问题。

证书过期错误不是MongoDB的问题,是Python环境信任的CA太旧
根本原因在于 Python 的 ssl 模块默认用系统自带的根证书(比如 Windows 的证书存储、macOS 的 Keychain、Linux 的 /etc/ssl/certs/ca-certificates.crt),而这些证书长期不更新。Let’s Encrypt 在 2021 年停用 DST Root CA X3,切换到 ISRG Root X1/X2;很多旧系统或 Python 安装没同步这个变更,导致无法验证新签发的证书链。
最直接有效的修复方式是用 certifi 提供的最新 CA 包:
- 先安装:
pip install certifi - 连接时必须显式传入:
tlsCAFile=certifi.where() -
certifi.where()返回的是本地cacert.pem绝对路径,不是 URL,也不能拼在 URI query 里 - 仅安装
certifi不生效——PyMongo 不会自动使用它,必须手动指定
别把 tlsCAFile 塞进连接字符串里,PyMongo 会静默忽略
错误写法:"mongodb+srv://u:p@host/?tls=true&tlsCAFile=./certs/ca.pem" —— PyMongo 4.0+ 完全不解析 tlsCAFile 这类 query 参数,它只认关键字参数(kwargs)。
正确写法是拆出来,用字典传参:
from pymongo import MongoClient import certifi <p>client = MongoClient( "mongodb+srv://user:pass@cluster.mongodb.net/", tls=True, tlsCAFile=certifi.where(),</p><h1>tlsAllowInvalidCertificates=False, # 生产环境必须关掉</h1><p>) </p>
-
tls=True是必需的,等价于旧版ssl=True,但新版本优先用tls - 不要同时设
ssl=True和tls=True,后者会覆盖前者,可能触发警告 - 如果用
mongodb+srv://,tls=True可省略(SRV 默认启用 TLS),但tlsCAFile仍需显式传
哪些情况 certifi.where() 会失效?得换系统级证书
以下场景下 certifi.where() 无效,必须改用系统证书路径或手动管理:
- 用 PyInstaller 打包时没把
certifi证书文件打进资源,运行时报IOError: No such file - 企业内网走 MITM 代理,需要把内部 CA 证书路径传给
tlsCAFile - Python 是通过系统包管理器安装(如 Ubuntu 的
python3-pymongo),其ssl模块硬绑定到/etc/ssl/certs/ca-certificates.crt—— 此时要更新系统证书:
• Ubuntu/Debian:sudo update-ca-certificates
• CentOS/RHEL:sudo update-ca-trust
• macOS(Homebrew Python):brew reinstall ca-certificates
• Windows:手动导入 ISRG Root X1/X2 和 Let's Encrypt R3 的.cer文件到“受信任的根证书颁发机构”
绝对不要用 ssl_cert_reqs=ssl.CERT_NONE
这是关闭证书验证,不是解决问题,后果严重:
- 连接会被中间人攻击劫持,密码和数据明文传输
- PyMongo 6.0+ 已弃用该参数,直接报错:
ValueError: ssl_cert_reqs cannot be CERT_NONE - 即使临时加了,也会触发
UserWarning: Insecure transport,CI 环境可能直接失败 - 开发调试时若实在卡住,可用
tlsAllowInvalidCertificates=True(仅限本地),但它只是跳过有效期和域名校验,仍做 TLS 加密——上线前必须删掉
真正容易被忽略的是冷启动问题:哪怕你代码改对了,Docker 启动时工作目录不对、CI 流水线没同步证书文件、或打包后 certifi.where() 返回路径不可读,都会导致 TLS 握手失败。验证前先确认 python -c "import certifi; print(certifi.where())" 输出的路径真实存在且可读。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











