必须用 http.ListenAndServeTLS 启动 HTTPS 服务,正确配置 cert.pem(含服务器证书和中间证书)与未加密的 key.pem;客户端应通过 RootCAs 或 VerifyPeerCertificate 安全校验,禁用 InsecureSkipVerify。

如何用 net/http 启动 HTTPS 服务(而非 HTTP)
Go 的 http.Server 本身不区分 HTTP/HTTPS,关键在监听方式:必须用 http.ListenAndServeTLS,不能用 http.ListenAndServe。否则即使配置了证书,也只会走明文 HTTP。
常见错误是把 PEM 文件路径写错、权限不足,或混淆了证书链顺序(cert.pem 必须包含服务器证书 + 中间证书,key.pem 是私钥,且不能加密)。
-
cert.pem中若漏掉中间 CA,浏览器会报x509: certificate signed by unknown authority - 私钥文件若带密码(如用
openssl genrsa -aes256生成),ListenAndServeTLS会直接 panic,需先用openssl rsa -in key.pem.enc -out key.pem去密 - 本地测试可用
generate_cert.go(Go 自带工具)快速生成自签名证书,但客户端需显式跳过验证(仅限开发)
客户端如何安全地调用 HTTPS 接口(绕过证书校验的坑)
默认情况下,http.DefaultClient 会严格校验证书。若服务端用自签名证书或域名不匹配,请求直接失败,报错 x509: certificate is valid for xxx, not yyy。
临时跳过验证的写法很常见,但极易误用于生产:
立即学习“go语言免费学习笔记(深入)”;
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr}
这等于完全放弃 TLS 安全性,中间人可篡改全部通信。真正安全的做法是:
- 用
tls.Config.RootCAs加载自定义 CA 证书池(certpool.AppendCertsFromPEM),只信任你指定的根 - 通过
tls.Config.VerifyPeerCertificate实现细粒度校验(例如检查 SAN 域名、证书有效期) - 永远不要在生产环境设
InsecureSkipVerify: true,CI/CD 流水线可加静态检查拦截该字段
crypto/tls 中 ServerName 和 GetCertificate 的实际作用
当一个 Go 服务要托管多个域名(SNI 场景),不能靠 Nginx 反向代理来分发证书——得在 Go 层自己处理。核心是 tls.Config 的两个字段:
-
ServerName是客户端发起连接时声明的目标域名(SNI 字段),服务端用它查该用哪张证书;但这个字段只在客户端配置里有意义,服务端不用设 -
GetCertificate是服务端回调函数,接收*tls.ClientHelloInfo,其中ServerName就是客户端传来的域名,你据此返回对应证书+私钥 - 若没设
GetCertificate,服务端只用Certificates[0],所有域名都返回同一张证书,SNI 失效
注意:证书必须提前加载进内存(比如从文件或 Vault 拉取),不能每次回调都读磁盘,否则高并发下性能骤降。
为什么 http.Request.TLS 有时为 nil,以及怎么判断真实加密状态
即使走的是 HTTPS 请求,req.TLS 也可能为 nil——最常见原因是反向代理(如 Nginx、Cloudflare)终止了 TLS,再以 HTTP 转发到 Go 后端。此时连接确实是加密的,但 Go 进程看到的只是普通 HTTP。
不能只靠 req.TLS != nil 判断是否加密,而应结合:
- HTTP 头
X-Forwarded-Proto: https(需代理可信且配置了该头) - 监听地址是否为
:443(不绝对,但可作为辅助依据) - 更可靠的方式是统一在入口层(如网关)注入
X-SSL-Client-Verify: SUCCESS等自定义头
如果业务逻辑强依赖 TLS 信息(比如提取客户端证书),必须确保 TLS 终止点就是 Go 进程自身,或者代理明确透传 SSL_CLIENT_CERT 等头。










