hsts不生效是因为runtls仅启用tls加密,未在响应头中设置strict-transport-security;需在gin中通过c.header().set()显式添加该头,且仅在https请求中(推荐用x-forwarded-proto判断),并在c.next()前执行。

为什么 RunTLS 启动 HTTPS 服务后 HSTS 还不生效
因为 RunTLS 只负责 TLS 层加密,不自动写入 Strict-Transport-Security 响应头。浏览器根本收不到这个头,就无法记住“只许走 HTTPS”这条策略。哪怕证书正确、端口 443 正常响应,只要没在响应中显式设置,HSTS 就等于没开。
在 Gin 中设置 HSTS 的正确位置和写法
HSTS 头必须用 c.Header().Set()(不是 Add()),且只在 HTTPS 请求中设置。最稳妥的判断方式是检查 c.GetHeader("X-Forwarded-Proto") == "https"——尤其当你用 Nginx 或 Cloudflare 终止 TLS 时,c.Request.TLS 往往为 nil,靠它会漏设。
-
c.Header("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload")必须在c.Next()之前执行(即响应体写出前) - 灰度期建议先用
max-age=300(5 分钟),确认全站无 HTTP 资源、子域 HTTPS 就绪、301 跳转也带该头后再升到 31536000 - 别依赖
github.com/unrolled/secure:它的STSPreload在某些版本里不生效,且无法保证includeSubDomains和preload同时存在
预加载(preload)上线前必须验证的三件事
提交到 hstspreload.org 不是加个头就完事。Go 服务返回的响应必须满足硬性条件,否则会被拒绝或后续踢出列表:
- 根域名(如
example.com)必须能直接通过 HTTPS 访问,并返回含includeSubDomains和preload的相同 HSTS 头 -
http://example.com/必须返回 301(不是 302),且该跳转响应也必须带Strict-Transport-Security头——Gin 默认不这么做,需单独写中间件处理 HTTP 端口的重定向 - 确保没有中间跳转链(比如
http → http → https),hstspreload 要求跳转路径严格为http → https单步完成
常见错误现象和排查点
浏览器 DevTools 的 Network 标签页里看不到 Strict-Transport-Security 头,或设置了但没生效,大概率是以下原因:
- 用了
c.Header().Add()导致重复添加,浏览器直接忽略整个头 - 在
c.Next()之后才调用c.Header().Set(),此时响应头已锁定,设置无效 - 只检查
c.Request.TLS != nil,而反向代理后该字段恒为nil,结果 HSTS 完全没设 - 证书链不全(比如只传了
cert.pem没传fullchain.pem),导致浏览器报错,用户根本打不开 HTTPS 页面,自然收不到 HSTS 头
真正麻烦的是:HSTS 一旦生效,max-age 时间内浏览器会强制跳过 HTTP,连调试都只能清缓存或换浏览器——所以灰度验证阶段绝不能跳过。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











