服务端证书动态选择依赖GetCertificate回调,它在每次TLS握手时根据ClientHello.ServerName精确匹配预加载的内存证书,不支持自动切换或多域名静态配置。

服务端证书动态选择靠 GetCertificate 回调
Go 的 tls.Config 不支持“多证书自动切换”,必须显式提供 GetCertificate 函数,由它在每次 TLS 握手时按需返回匹配的 tls.Certificate。不设这个字段,就只能用静态的 Certificates 字段——只支持单域名或固定证书。
GetCertificate 必须根据 ClientHello.ServerName 做路由
客户端在 TLS 握手初期会发 ServerName(即 SNI),这是唯一可靠的路由依据。你不能靠请求路径、Header 或 IP 来选证书——那些在 TLS 层完成前根本不可见。
-
GetCertificate函数签名是func(*tls.ClientHelloInfo) (*tls.Certificate, error),入参*tls.ClientHelloInfo里只有ServerName、SupportedProtos等有限字段 - 若客户端没发 SNI(比如老客户端或 IP 直连),
ServerName为空字符串,这时应 fallback 到默认证书 - 证书匹配必须精确:请求
api.example.com就不能返回www.example.com的证书,否则握手失败并报x509: certificate is valid for www.example.com, not api.example.com
证书池要预加载,别在回调里反复解析文件
每次握手都读磁盘、调 tls.LoadX509KeyPair 是严重性能瓶颈,且可能因文件锁或权限问题失败。正确做法是启动时一次性加载所有证书到内存 map 中。
- 用
map[string]tls.Certificate缓存,key 是域名(小写标准化),value 是已解析的tls.Certificate - 私钥文件权限必须是
0600,否则LoadX509KeyPair静默失败,返回空结构体,导致后续握手 panic - 如果证书链含中间 CA,确保
cert.pem文件里是“服务器证书 + 中间证书”拼接顺序,否则浏览器可能提示“证书不完整”
HTTP/2 要求证书支持 ALPN,别漏掉 NextProtos
Go 默认启用 HTTP/2,但若 tls.Config.NextProtos 被覆盖(比如设成 []string{"h2"} 却没加 "http/1.1"),会导致旧客户端无法降级,直接断连。
立即学习“go语言免费学习笔记(深入)”;
- 安全写法是显式保留默认值:
NextProtos: []string{"h2", "http/1.1"} - 若用自定义
GetCertificate,tls.Config.NextProtos仍需全局设置——它不随证书变化,是整个监听器共用的 - 证书本身也要支持所声明的 ALPN 协议;某些私有 CA 签发的证书若没带
ALPN扩展字段,可能被某些客户端拒绝
最易忽略的是:SNI 匹配逻辑必须覆盖通配符(如 *.example.com),但 Go 标准库不解析通配符,得你自己实现字符串比对;另外,证书重载(比如 ACME 自动续期)需要外部信号触发 map 更新,而 GetCertificate 回调本身无重载机制——这点常被当成“热更新”幻觉。


















