gin 的 runtls 仅支持 pem 格式证书且路径必须绝对,私钥权限需为 0600,应手动构建 http.server 设置超时与 tls 版本,http 跳转需另起 goroutine 启独立服务,let’s encrypt 证书续期需外部 acme 客户端支持。

证书文件必须是 PEM 格式且路径绝对
Gin 的 RunTLS 只认 PEM(Base64 编码的 BEGIN CERTIFICATE 和 BEGIN RSA PRIVATE KEY),不支持 PFX、DER 或其他格式。传错格式会静默 panic,错误信息常为 tls: failed to find any PEM data in certificate input,实际是格式不对,不是文件不存在。
路径必须绝对:相对路径如 "cert.pem" 在 systemd、Docker 或 IDE 启动时极易失效。正确做法是用 filepath.Abs("cert.pem") 获取绝对路径,并在启动前用 os.Stat() 显式校验文件存在且可读。
- 开发可用
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes快速生成测试 PEM 对 - 生产必须用 Let’s Encrypt 的
fullchain.pem(含中间 CA)+privkey.pem;只传cert.pem会导致客户端报x509: certificate signed by unknown authority - 私钥权限必须是
0600(Linux/macOS),0644会被 Go 拒绝加载,错误提示却指向证书匹配失败 —— 这是高频踩坑点
RunTLS 不能替代完整 http.Server 配置
router.RunTLS(":443", "cert.pem", "key.pem") 是最简启动方式,但底层调用的是 http.ListenAndServeTLS,它不支持设置超时、TLS 版本限制或连接池参数。生产环境一旦出现慢连接耗尽端口,就只能重启服务。
真正可控的做法是手动构造 http.Server:
server := &http.Server{
Addr: ":443",
Handler: router,
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 60 * time.Second,
TLSConfig: &tls.Config{
MinVersion: tls.VersionTLS12,
CurvePreferences: []tls.CurveID{tls.CurveP256},
},
}
server.ListenAndServeTLS("cert.pem", "key.pem")
- 必须显式设置
TLSConfig.MinVersion,否则默认允许 TLS 1.0/1.1,存在已知漏洞 -
IdleTimeout防止 Keep-Alive 连接长期空闲占用资源 - 别再调
router.RunTLS(),否则两个服务抢同一端口,直接 panic
HTTP → HTTPS 跳转必须另起 goroutine 启独立服务
Gin 的 RunTLS 只监听 HTTPS 端口,完全收不到 HTTP 请求。指望中间件拦截跳转是无效的 —— HTTP 请求根本到不了 Gin 路由层。
正确做法是启动两个独立服务:
go func() {
http.ListenAndServe(":80", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "https://"+r.Host+r.RequestURI, http.StatusMovedPermanently)
}))
}()
router.RunTLS(":443", "cert.pem", "key.pem")
- 跳转必须用
http.StatusMovedPermanently(301),不是默认的 302;否则搜索引擎反复抓取 HTTP 版本,影响 SEO -
r.Host必须透传,否则跳转可能变成https://localhost/xxx,尤其在反向代理后容易出错 - 若部署在 Nginx 或云 LB 后,这一步应由前置代理完成(如 Nginx 的
return 301 https://$host$request_uri),Gin 内无需重复实现
Let’s Encrypt 证书无法自动续期,Gin 不提供 ACME 支持
Gin 本身不集成 ACME 协议,也不处理证书自动申请与轮换。把 fullchain.pem 和 privkey.pem 往 RunTLS 一塞,服务能跑,但 90 天后证书过期,连接直接被客户端拒绝,没有警告、没有 fallback。
- 生产环境必须引入外部 ACME 客户端(如
certmagic或lego),并在启动时动态加载证书 - 不要用 cron +
systemctl reload方式续期,Gin 服务无法热加载新证书;需配合graceful restart或进程管理器(如 systemd 的ExecReload) - 自签名或私有 CA 场景下,重点是让客户端信任根证书(如分发
ca.crt),而非纠结域名验证
证书路径、权限、格式、协议版本、跳转逻辑、续期机制——这五处任一缺失都可能导致 HTTPS 表面跑通,实则线上不可靠。尤其注意私钥权限和 301 跳转的 Host 透传,这两个点在线上最容易被忽略。











