Codex 报 self signed certificate in certificate chain,说的不是你自己签了什么证书。它的意思是:服务器交出来的证书链,最顶上那张根证书不在报错进程的信任库里。在公司电脑或公司网络上,这张根证书多半属于公司的 HTTPS 流量检查代理(也叫 TLS 解密代理)或安全软件,它们会用公司自己的根证书把连接重新签发一遍。
解决办法是把这张根证书交给报错的那个进程,而不是关掉证书校验。如果报错出现在 codex login 或 Codex CLI 的请求里,向 IT 拿到公司根证书的 PEM 文件后这样设置:
export CODEX_CA_CERTIFICATE="$HOME/certs/corp-root.pem"
codex loginCodex 的认证文档写明,网络里有公司 TLS 代理或私有根 CA 时,登录前把 CODEX_CA_CERTIFICATE 指向 PEM 文件;没设这个变量时 Codex 会改读 SSL_CERT_FILE。
如果设了仍然报错,或者报错出现在桌面应用、MCP 服务、Codex 替你执行的 curl 或 npm 里,原因通常是报错的进程不是 Codex 的客户端:这几类进程各读各的信任设置,一个变量管不了全部。先按下面的对照找到自己那一行。
先看是哪个进程报的错:四种现象对应四套信任设置
| 你看到的现象 | 报错的进程 | 它读的信任设置 | 该做什么 |
|---|---|---|---|
codex login 失败,或 CLI 发请求、连 WebSocket 时报错 | Codex 自己的客户端 | CODEX_CA_CERTIFICATE,没有就读 SSL_CERT_FILE,再没有只用系统根证书 | 设置 CODEX_CA_CERTIFICATE 后重新登录 |
桌面应用的 Remote Control 连不上,日志里有 SELF_SIGNED_CERT_IN_CHAIN | 桌面应用里的 Node 层 | Node 的根证书:NODE_USE_SYSTEM_CA=1、NODE_EXTRA_CA_CERTS | 有用户报告带 NODE_USE_SYSTEM_CA=1 启动后恢复 |
| 某个 MCP 服务连接或调用失败 | HTTP 型是 Codex 的 MCP 客户端;stdio 型是服务自己的进程 | HTTP 型同第一行;Node 写的 stdio 服务读 NODE_EXTRA_CA_CERTS | 在 config.toml 里把变量传给这个服务 |
Codex 执行的命令报 curl: (60) SSL certificate problem: self signed certificate in certificate chain | 沙箱里的那个命令 | 工具自己的 CA 设置,例如 curl 的 CURL_CA_BUNDLE | 给命令一个显式的 CA 文件,并确认变量传进了沙箱 |
分不清是哪一行时,看报错出现的位置:登录阶段或界面顶部的连接错误属于第一行;带 [remote-control-transport] 的日志属于第二行;出现在某个工具调用结果里、前面带着 curl: 或 npm 字样的,属于第三、四行。
self signed 是什么意思:链顶的根证书不在信任库里
self signed certificate in certificate chain 是 OpenSSL 的校验错误 19:对方给出的证书链完整,但链顶是一张自签名的根证书,而这张根证书不在当前进程的信任库里。所有根证书都是自签名的,区别只在于你的信任库里有没有它。
公司网络上的常见成因是 HTTPS 流量检查:代理在中间把连接解开,再用公司的根证书重新签一张证书给你。技术上它相当于中间人,但这通常是公司明确部署的安全策略。浏览器不报错,是因为 IT 已经把公司根证书装进了系统信任库;Codex 的某个进程报错,是因为那个进程没有读系统信任库,或者读不到。

另外几种成因也会得到同一句话:杀毒软件的 HTTPS 扫描、用私有 CA 签发证书的自建网关或 MCP 服务,以及指向内部地址的自定义 base URL。
报错如果是 self-signed certificate(错误 18,没有 "in certificate chain"),情况不同:那是服务器证书本身自签名,常见于内部测试服务,要信任的是那张服务器证书或它所属的私有 CA。
用 openssl s_client 确认是谁签的证书,并拿到根证书 PEM
先确认连接确实被重签,以及是被谁重签的。把主机名换成报错或日志里出现的那个:
openssl s_client -connect chatgpt.com:443 -showcerts </dev/null看输出里最后一张证书的 i:(issuer,签发者)一行,以及末尾的 Verify return code:
- 签发者是公司名或安全产品名:连接被公司代理重签,需要的就是这张根证书。
- 签发者是公开的证书机构,返回码是
0 (ok):这条连接没问题,出错的是另一个主机名,或另一个读不到系统信任库的进程。 - 签发者既不是公司,也不是你认识的安全产品:停下来,把输出发给 IT,不要去信任一张来历不明的根证书。

openssl s_client 只负责展示证书链,拿根证书有三条路:
- 向 IT 要 PEM 格式的公司根证书,这是最稳妥的来源。
- macOS:打开"钥匙串访问",在"系统"钥匙串里找到公司根证书,用"文件 → 导出项目"存成
.pem。 - Windows:运行
certmgr.msc,在"受信任的根证书颁发机构"里找到它,导出时选"Base-64 编码 X.509 (.CER)",这种格式就是 PEM。
文件用文本编辑器打开,应当以 -----BEGIN CERTIFICATE----- 开头。放进 Codex 之前先单独验证它确实能解决问题:
openssl s_client -connect chatgpt.com:443 -CAfile "$HOME/certs/corp-root.pem" </dev/null 2>/dev/null | grep "Verify return code"输出 Verify return code: 0 (ok) 说明证书对了;仍是 19,说明拿到的不是链顶那张,或者代理用的是另一张根证书。
Codex CLI 登录和请求报错:设置 CODEX_CA_CERTIFICATE
Codex 自己的客户端按固定顺序找证书:先读 CODEX_CA_CERTIFICATE,没有就读 SSL_CERT_FILE,都没有就只用系统根证书。自定义证书是和系统根证书一起加载的,所以 PEM 文件里只放公司根证书就够,不必自己拼一份完整的证书包。
这套设置不只管登录。按 openai/codex 的 PR #14239(2026 年 3 月 13 日合并)的说明,Codex 向外发起的 HTTPS 请求和安全 WebSocket 连接都读同一组变量,包括后端请求、云端任务、MCP 的 HTTP 客户端和实时 WebSocket。很旧的版本只在登录时读自定义证书或完全不读,这种情况先升级 Codex 再排查。
要让变量每次都生效,把它写进 shell 配置文件:
# macOS / Linux:写入 ~/.zshrc 或 ~/.bashrc
export CODEX_CA_CERTIFICATE="$HOME/certs/corp-root.pem"# Windows PowerShell:写入用户环境变量,重开终端后生效
setx CODEX_CA_CERTIFICATE "C:\certs\corp-root.pem"有三点会让设置看起来"没生效":
- 变量必须出现在启动 Codex 的那个进程的环境里。从 Dock 或开始菜单启动的 IDE 不一定读你的 shell 配置文件;IDE 里的 Codex 仍报错时,从已经设好变量的终端启动 IDE 再试。
- 路径写错或文件不是 PEM 时,Codex 的报错会直接点出变量名和路径,按提示改。
- 你的环境里如果早就有一个指向别处的
SSL_CERT_FILE,而CODEX_CA_CERTIFICATE没设,Codex 读的就是那个文件。
设备码登录(codex login --device-auth)帮不上忙:它省掉的是本机浏览器回调,登录请求照样走 HTTPS,照样要过证书校验。
桌面应用 Remote Control 连不上:试 NODE_USE_SYSTEM_CA=1
桌面应用里有一层基于 Node 的代码,Node 默认只信任自己内置的根证书,不看系统钥匙串。所以会出现这种情况:公司根证书已经在系统钥匙串里被信任,curl 和浏览器都正常,桌面应用的 Remote Control 却报 self signed certificate in certificate chain,日志里是:
[remote-control-transport] remote_control_websocket.connect_failed_before_open这是 openai/codex 的 issue #43489 里一位用户在 2026 年 9 月 7 日报告的现象(应用版本 26.901.51231,macOS 26.6.1)。报告者的做法是带着 NODE_USE_SYSTEM_CA=1 从终端启动应用,让 Node 改读系统钥匙串:
NODE_USE_SYSTEM_CA=1 /Applications/ChatGPT.app/Contents/MacOS/ChatGPT需要知道的边界:这是一位用户的报告,截至 2026 年 10 月 2 日 issue 仍未关闭,也没有维护者确认。它成立的前提是公司根证书已经在系统钥匙串里并被设为信任。NODE_USE_SYSTEM_CA 的含义见 Node.js 的命令行文档:让 Node 加载操作系统的证书库(macOS 钥匙串、Windows 证书存储、Linux 上 OpenSSL 的默认位置)。桌面应用的 Node 层是否读 CODEX_CA_CERTIFICATE,以及 Windows、Linux 上的桌面应用是否有同样的问题,公开文档和这个 issue 里都没有记载。
这个启动方式只对当次启动有效。它管用的话,把你的版本和现象补充到 issue 里,并让 IT 知道桌面应用需要这样启动。
MCP 服务报证书错误:HTTP 型跟随 Codex,stdio 型要单独传变量
MCP 服务分两种,报错的进程不同。
通过 URL 连接的 HTTP 型服务,发请求的是 Codex 的 MCP 客户端,它读的就是 CODEX_CA_CERTIFICATE / SSL_CERT_FILE。公司内部用私有 CA 签发的 MCP 服务也属于这一类:把那个私有 CA 的根证书追加进同一个 PEM 文件即可,一个文件可以放多张证书。
stdio 型服务是 Codex 启动的一个独立进程,访问外部接口的是它自己。用 Node 写的服务(多数用 npx 启动的都是)读 NODE_EXTRA_CA_CERTS,这个变量把文件里的证书加到 Node 内置的根证书之外。在 config.toml 里把它传给对应的服务,写法见 Codex 的 MCP 文档:
[mcp_servers.your-server]
command = "npx"
args = ["-y", "your-mcp-package"]
[mcp_servers.your-server.env]
NODE_EXTRA_CA_CERTS = "/Users/you/certs/corp-root.pem"NODE_EXTRA_CA_CERTS 有两个容易踩的地方:它只在进程启动时读一次,改完要重启 Codex 让服务重新拉起;文件格式有问题时 Node 只打印一条警告就继续运行,看起来像"设置了但没用",这时回头检查 PEM 文件本身。
其他语言写的 MCP 服务有各自的 CA 设置,查那个运行时或服务本身的文档。
沙箱里的 curl 报 curl: (60):给命令一个显式的 CA 文件
Codex 在沙箱里执行的命令是另一批进程,CODEX_CA_CERTIFICATE 管不到它们。在 macOS 上还多一层原因:按 issue #11182 报告者的分析(2026 年 2 月 9 日,codex-cli 0.98.0),系统自带的 curl 在 /etc/ssl/cert.pem 里找不到根证书时会去问钥匙串的信任服务 com.apple.TrustEvaluationAgent,而沙箱拒绝了这次查询。结果是同一条 curl 命令在你自己的终端里成功,由 Codex 执行就报:
curl: (60) SSL certificate problem: self signed certificate in certificate chain这个 issue 仍未关闭,维护者没有给出处理办法。能做的是不依赖钥匙串,直接给工具一个 CA 文件,这是 curl 本身的标准用法,issue 里并没有验证它在沙箱中一定奏效:
# curl 的 CA 文件会取代默认证书包,所以把系统证书包和公司根证书合在一起
cat /etc/ssl/cert.pem "$HOME/certs/corp-root.pem" > "$HOME/certs/corp-bundle.pem"
export CURL_CA_BUNDLE="$HOME/certs/corp-bundle.pem"单次命令也可以写 curl --cacert "$HOME/certs/corp-bundle.pem" https://…。 由 Codex 执行的 npm 和其他 Node 工具同理,用 NODE_EXTRA_CA_CERTS。
还要确认变量真的传进了沙箱。config.toml 里的 shell_environment_policy 决定 Codex 把哪些环境变量交给它启动的命令,你在 shell 里 export 的变量不一定到得了那里。可以用 set 显式写入:
[shell_environment_policy]
set = { CURL_CA_BUNDLE = "/Users/you/certs/corp-bundle.pem", NODE_EXTRA_CA_CERTS = "/Users/you/certs/corp-root.pem" }其余键(继承范围、过滤规则)的取值以 Codex 的高级配置文档为准;沙箱和批准策略本身怎么配,见 Codex 沙箱与 config.toml 的权限、批准和网络配置。
为什么不要用 NODE_TLS_REJECT_UNAUTHORIZED=0 关掉校验
因为它没有解决"进程不认识公司根证书",而是让进程不再检查任何证书。Node.js 文档对 NODE_TLS_REJECT_UNAUTHORIZED=0 的说明是:它会关闭证书校验,使 TLS 连接暴露在中间人攻击之下。npm config set strict-ssl false、curl -k、--insecure 是同一类做法。
对 Codex 来说代价很具体:经过这些连接的有登录凭据、访问令牌和你的代码。校验关掉之后,任何能插到你和服务器之间的设备都可以冒充对方,而你看不到任何提示。加一张根证书只是多信任一个你知道来历的签发者,其余校验照常进行;两者不是同一件事的快慢两种做法。
验证证书生效,以及什么时候该找 IT
验证只需要重做刚才失败的那一步,并且在同一个环境里做:
- CLI:新开一个终端,
echo $CODEX_CA_CERTIFICATE确认变量在,再运行codex login或重新发一次请求。 - 桌面应用:用带变量的方式启动后重新连接 Remote Control,日志里不再出现
connect_failed_before_open。 - MCP 服务:重启 Codex,让服务重新拉起,再调用一次它的工具。
- 沙箱命令:让 Codex 重新执行同一条
curl,而不是你自己在终端里跑,两者的环境不同。
出现下面的情况,停下来找 IT,不要继续自己试:
- 拿不到根证书,或者公司规定不允许自行导出。
openssl显示的签发者不是公司,也不是你认识的安全产品。- 根证书确认无误,
openssl -CAfile也返回0 (ok),但 Codex 仍然报错。把报错原文、出错的主机名和openssl s_client的输出一起交给 IT,问题可能在代理对 WebSocket 或特定域名的处理上。 - 设备由公司托管,环境变量或配置文件改不了。
证书问题解决后如果登录仍然失败,按 Codex 报错 Token Exchange Failed 的分阶段排查继续;任务中途反复断线,见 Codex 一直 Reconnecting 时怎么定位是哪一层断了。
根证书已经装进钥匙串,为什么 Codex 还报这个错?
因为装进系统信任库只对读它的进程有效。浏览器和 macOS 自带的 curl 会读钥匙串;Node 默认只用内置根证书,要靠 NODE_USE_SYSTEM_CA=1 或 NODE_EXTRA_CA_CERTS 才认;沙箱里的命令按用户报告访问不到钥匙串的信任服务。Codex 自己的客户端会加载系统根证书,但设置 CODEX_CA_CERTIFICATE 更直接,也不依赖系统信任库在各平台上的差异。
登录时报 Token exchange failed,也是证书的问题吗?
有可能。issue #6849 里,旧版 CLI(0.58.0)在带自定义 CA 的公司代理后面登录,报的是 Token exchange failed: error sending request for url (https://auth.openai.com/oauth/token ),没有出现 "self signed" 字样,根因却相同。用上面的 openssl s_client 命令连 auth.openai.com:443 看签发者:是公司代理,就升级 Codex 并设置 CODEX_CA_CERTIFICATE;签发者是公开证书机构,原因在别处。



