服务端必须显式设置ClientAuth为tls.RequireAndVerifyClientCert且ClientCAs非空,否则降级为单向TLS;私钥权限须为0600,证书链需完整,RootCAs须与服务端CA字节级一致。

服务端必须显式设 ClientAuth 为 tls.RequireAndVerifyClientCert
不设这个,Go 就不会向客户端索要证书——连接看起来是 HTTPS,但实际只是单向 TLS,所谓“双向”根本没生效。很多团队配完发现客户端连得上、服务端却没校验证书,卡在这里。
常见错误配置:tls.VerifyClientCertIfGiven(允许无证书连接)或干脆不设 ClientAuth(默认值是 tls.NoClientCert)。两者都不满足强制双向要求。
-
http.ListenAndServeTLS无法自动注入ClientAuth配置,必须手动构造http.Server实例 -
srv.TLSConfig = &tls.Config{...}中必须包含ClientAuth: tls.RequireAndVerifyClientCert -
ClientCAs字段不能为空,必须加载服务端信任的 CA 根证书池(*x509.CertPool),不是客户端证书本身 - 服务端自己的证书链走
Certificates字段,与ClientCAs完全解耦
证书文件权限和格式踩坑点
私钥文件权限不是警告,是硬性门槛:必须是 0600,否则 Go 会静默失败并 fallback 到 HTTP(错误不报,连接直接降级)。
证书链不完整是另一个高频问题:服务端 Certificates 必须是完整 PEM 链(域名证书 + 所有中间 CA),只放 server.crt 不够;客户端证书也一样,client.crt 文件里得包含 leaf + intermediate。
- 用
openssl verify -CAfile ca.crt server.crt检查服务端证书链是否可被 CA 验证 - 私钥不能加密(带密码),
tls.LoadX509KeyPair不支持解密,返回空*tls.Certificate且不报错 - Windows 路径写法必须用正斜杠(
"C:/certs/server.crt")或原始字符串(`C:\certs\server.crt`),混用反斜杠会提示文件找不到
GoLand 调试时如何验证 mTLS 是否真生效
别信日志里“TLS handshake OK”——那只是单向握手成功。真正验证双向是否启用,得看客户端证书有没有被校验。
最直接办法:在 http.HandlerFunc 或 http.Server.Handler 里加一行断点,检查 r.TLS.VerifiedChains 是否非空。如果为空,说明服务端压根没收到或没验客户端证书。
- GoLand 的 Debug 模式下,把鼠标悬停在
r.TLS上,展开看VerifiedChains和PeerCertificates - 若
PeerCertificates长度为 0,大概率是ClientAuth没设对,或客户端根本没发证书 - 若长度 > 0 但
VerifiedChains为空,说明客户端证书签发者不在ClientCAs里,信任链断了
为什么 curl 测试老失败,但 Go 客户端能连上
curl 默认不发客户端证书,也不加载自签名 CA,所以报 x509: certificate signed by unknown authority 或 tls: failed to verify client's certificate 是常态,不代表服务端有问题。
正确测试命令必须带三要素:-cert、-key、-CAfile,且 -CAfile 必须是服务端所信任的 CA 根证书(不是服务端自己的 server.crt)。
- 正确命令:
curl -v --cert client.crt --key client.key --cacert ca.crt https://localhost:8443/health - 错误示范:
--cacert server.crt(这是服务端证书,不是 CA)或漏掉--cacert(curl 不信任私有 CA) - GoLand 里跑客户端代码时,确保
http.Transport.TLSClientConfig.RootCAs加载的是同一个ca.crt
openssl s_client 或 curl 把证书组合跑通再说。


















