Kratos 中必须在 kratos.NewApp 时注入已配置 TLSConfig 的 *http.Server 实例;单向 TLS 需 certFile+keyFile,mTLS 还需 TLSConfig.ClientAuth(如 tls.RequireAndVerifyClientCert)和 ClientCAs(加载 ca.crt);证书格式、私钥权限(0600)、CA 根证书加载缺一不可。

kratos.NewApp 里必须传入自定义 *http.Server 实例
Kratos 没有 RunTLS 或 EnableMTLS 这类封装方法,所有 TLS 行为都依赖标准 net/http.Server。你不能对 app.HttpServer() 返回的 http.Handler 做任何 TLS 包装——Go 1.26+ 类型校验会直接编译失败。唯一可行路径是:在 kratos.NewApp 时通过 server.Server 选项注入一个已配置好 TLSConfig 的 *http.Server。
常见错误包括:
- 试图用
http.ListenAndServeTLS包裹 Kratos 的Handler,报错:cannot use ... as http.Handler - 复用 HTTP 端口(如
":8080")跑 mTLS,导致accept tcp: operation not permitted - 把
TLSConfig.ClientAuth设成tls.RequireAnyClientCert却没配ClientCAs,服务启动后拒绝所有请求且无明确日志
http.Server.TLSConfig 必须显式设置 ClientAuth 和 ClientCAs
单向 TLS 只需 certFile + keyFile;mTLS 要求服务端主动验证客户端证书,因此必须配置以下两项:
-
TLSConfig.ClientAuth:至少设为tls.RequireAndVerifyClientCert(推荐),或tls.VerifyClientCertIfGiven(调试用) -
TLSConfig.ClientCAs:必须是*x509.CertPool,加载的是 CA 根证书(ca.crt),不是客户端证书本身
示例片段:
pool := x509.NewCertPool()
ca, _ := os.ReadFile("ca.crt")
pool.AppendCertsFromPEM(ca)
srv := &http.Server{
Addr: ":8443",
TLSConfig: &tls.Config{
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: pool,
},
}
注意:ClientCAs 若为空或加载失败,服务会静默拒绝连接,错误日志仅显示 tls: client didn't provide a certificate,容易误判为客户端问题。
证书文件权限和格式必须严格满足 crypto/tls 要求
Kratos 不做任何证书预处理,完全交由 Go 标准库 crypto/tls 加载。这意味着:
-
keyFile必须是未加密的 PEM 私钥(生成时加-nodes),否则 panic 报错极简:tls: failed to parse private key -
certFile必须含完整链:域名证书 + 中间 CA(如 Let’s Encrypt 的fullchain.pem),只放cert.pem会导致 Java/iOS/curl 验证失败 - Linux/macOS 下
keyFile权限必须为0600,否则crypto/tls直接拒绝读取,现象是进程无日志退出
客户端证书验证失败时,错误发生在 TCP 层,不进业务逻辑
mTLS 握手失败(如客户端没发证书、证书过期、CA 不匹配)时,连接会在 TLS 层被终止,http.Handler 完全收不到请求。你不会看到 400/403 响应,也不会触发任何中间件或路由逻辑。
排查建议:
- 用
openssl s_client -connect host:port -cert client.crt -key client.key -CAfile ca.crt手动测试握手是否成功 - 检查服务端日志是否有
http: TLS handshake error开头的行(通常带具体原因) - 确认客户端证书的
Extended Key Usage包含clientAuth,否则某些 TLS 栈会拒绝使用
真正难调的点不在代码里,而在证书链完整性、私钥权限、CA 根证书是否被正确加载这三处——它们出问题时,表现都是“连接被拒”,但根本原因完全不同。











