"The remote certificate is invalid according to the validation procedure."
如果你在 Azure APIM 自建网关(self-hosted gateway)容器日志里看到这条报错,而且你的后端 API 用的又是自签名证书,那你大概率正卡在一个很常见的坑里:自建网关没法信任这个证书,TLS 握手一直过不去。
我先交代一下我的实际环境。生产环境里,我负责维护的 API 网关是基于 Azure API Management 的自建网关,部署在内网 Kubernetes 集群里。后端服务里有一个 Apache NiFi 做数据接入,HTTPS 端口上挂的是内部生成的 PKCS12 自签名证书。自建网关转发请求到 NiFi 的时候,本来应该验证 NiFi 的服务端证书是否可信,结果每次都在 TLS 握手环节被卡住,日志里的证书校验报错一条接一条。
我一开始觉得这只是个"把证书塞进容器信任库"的小事,结果试遍了网上能找到的常见方案,全部失败。这篇文章不聊那种"一配就成功"的顺利攻略,而是专门把这些不成功方案掰开揉碎讲清楚,再给出我最终验证过、能在生产环境落地的做法。适合正在部署自建网关、又必须对接私有 CA 或自签名证书后端的同学,尤其是 Docker 和 Kubernetes 环境都跑过一遍的实践型读者。
1. 自建网关的证书信任问题,到底发生在哪几个环节?
1.1 数据面与控制面分离,导致证书管理天然分两套
Azure APIM 大家比较熟悉的是托管网关:Azure 云上帮你把整个网关运行环境搞定,你只需要上传证书、配策略、发布 API。但当我们选择自建网关时,数据面就移到了你自己的容器环境里,控制平面仍然留在 Azure 云端。这是理解证书问题的大前提。
自建网关容器本身是"半黑盒"。它有自己独立的操作系统文件系统,TLS 握手、证书链校验这些底层动作,实际上都发生在容器内的各个组件里。系统证书信任库在哪个目录、有哪些 CA 被预装,完全取决于官方镜像的基础操作系统,而不是取决于你在 Azure 门户里配了什么。
这带来的直接后果就是:你通过 Azure APIM 控制面上传的证书,和自建网关容器内实际用来做 TLS 校验的信任库,是两套东西。很多不成功方案,追根溯源都是把它们混为一谈了。
1.2 三个必须区分开的证书校验场景
我在排障的时候,把证书校验拆成了三个方向,这里直接给大家一个速查表。
| 场景 | 方向 | 证书类型 | 默认行为 | 信任库位置 |
|---|---|---|---|---|
| 客户端 → 网关 | 入站 | 网关服务端证书 | 客户端验证网关 | 客户端系统信任库 |
| 网关 → 后端 | 出站 | 后端服务端证书 | 自建网关容器验证后端 | 网关容器系统信任库 |
| 网关 → Azure 控制面 | 出站 | Azure TLS 证书 | 自建网关容器验证 Azure | 网关容器系统信任库 |
三个场景里,最常被提及的是"网关到后端"。自建网关接收客户端请求后,会以 HTTPS 方式向后端发起新连接。如果后端用的是自签名 CA 签发的证书,而网关容器里的系统信任库中没有这个 CA,TLS 校验就会失败。这个场景也是我处理 NiFi 时遇到的情况。
第二个场景"客户端到网关"方向相反。客户端访问自建网关的 HTTPS 端点时,如果网关用的是自签名证书,客户端就会报不信任。这个通常不是往网关容器里塞证书能解决的,而是要把根证书装到客户端那一侧,或者给网关配一张由企业 CA 或公共 CA 签发的正式证书。
第三个场景容易被忽略,但一旦出问题会更隐蔽:自建网关需要与 Azure 控制平面保持长连接,拉取配置、上报状态。正常情况下这个链路用的 Azure 公共 CA 证书,没毛病;但如果企业网络里存在 SSL 拦截、出口代理重新签发证书的情况,网关容器同样需要信任那个内网 CA,否则联调阶段连网关注册成功都看不到。
我在踩坑过程中发现,大多数人(包括我)只盯着"网关到后端"这一个场景,但实际改动容器信任库时,会影响后面两个场景的行为。所以要动手之前,先明确自己到底在解决哪个方向的问题,不然很容易出现"解决了 A,搞坏了 B"的情况。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 不成功方案拆解:看着合理,为什么就是不行?
2.1 方案一:给容器设置 SSL_CERT_FILE 环境变量
这是很多人第一个想到的办法。既然系统不认这个自签名证书,那我就通过环境变量告诉 TLS 库"用这个文件作为 CA 证书"。
我当时也这么干了:
bash复制docker run \
-e SSL_CERT_FILE=/certs/private-ca.pem \
-e SSL_CERT_DIR=/certs/ \
-v $(pwd)/private-ca.pem:/certs/private-ca.pem \
mcr.microsoft.com/azure-api-management/gateway:latest
结果很打脸:在容器里执行 curl https://nifi-server:8443 是通的,但网关转发请求时还是报证书校验失败。
原因在于,自建网关容器内部不是一个单进程应用,而是多个组件协同工作。不同组件使用的 TLS 库不一样,对环境变量的支持程度也不一样。OpenSSL 命令行工具会读取 SSL_CERT_FILE,但网关内部的 .N
