Go HTTP Server 默认不设置 HSTS 头,需手动在 handler 开头通过 w.Header().Set() 注入,推荐中间件统一处理,并确保重定向响应也携带该头。

Go HTTP Server 默认不设置 HSTS 头
Go 的 http.Server 不会自动添加 Strict-Transport-Security 响应头,哪怕你用 HTTPS 反向代理或 TLS 直连。浏览器因此不会强制后续请求走 HTTPS,中间人攻击风险仍在。这不是 bug,是设计取舍:Go 标准库坚持“显式优于隐式”。
实操建议:
- 必须手动在响应链中插入 HSTS 头,推荐在
http.Handler中间件层统一处理 - 不要依赖反向代理(如 Nginx)代为添加——除非你 100% 确保所有流量都经过它且配置无误
-
max-age至少设为31536000(1 年),低于31536000的值可能被 Chrome 忽略(仅限预加载列表域名) - 开发环境建议用
max-age=300测试,避免误配后本地调试困难
使用 http.StripPrefix + 自定义 Handler 添加 HSTS
最轻量、无第三方依赖的做法:包装原始 handler,在写响应前注入头。适用于标准 http.ServeMux 或自定义 http.Handler。
常见错误现象:Header().Set() 在 WriteHeader() 或 Write() 调用后失效,导致 HSTS 头丢失。
立即学习“go语言免费学习笔记(深入)”;
实操建议:
- 务必在 handler 函数开头调用
w.Header().Set("Strict-Transport-Security", "...") - 若用了
http.StripPrefix,HSTS 应加在被包装的 handler 内部,而非外层 - 示例值:
"max-age=31536000; includeSubDomains; preload"——preload需确认已提交到 hstspreload.org,否则无效 - 注意:如果 handler 中有重定向(如
http.Redirect),需确保重定向响应也带 HSTS 头,否则跳转后首屏不生效
用 middleware 封装 HSTS(如 Gin / Echo 场景)
Gin 和 Echo 等框架的中间件机制更适合集中管理安全头。但要注意框架默认行为差异:Gin 的 c.Header() 在 c.Next() 后仍可写,Echo 则要求在 next(c) 前设置,否则被覆盖。
实操建议:
- Gin 中推荐用
c.Header("Strict-Transport-Security", value)放在c.Next()前 - Echo 中必须在
next(c)前调用c.Response().Header().Set(...),否则响应已提交 - 避免在中间件里重复添加:检查
w.Header().Get("Strict-Transport-Security")是否非空,防止多层中间件叠加 - 生产环境禁用
includeSubDomains除非你确定所有子域都支持 HTTPS,否则会导致子域彻底不可访问
HSTS 与 HTTP/HTTPS 混合部署的兼容性陷阱
如果你的服务同时监听 HTTP(80)和 HTTPS(443),HSTS 对 HTTP 请求无效,且浏览器只在 HTTPS 响应中接受并缓存该头。更危险的是:一旦用户首次通过 HTTP 访问,就收不到 HSTS,后续可能持续走明文。
实操建议:
- HTTP 端口必须做 301 重定向到 HTTPS,且重定向响应也要带 HSTS 头(虽然浏览器不存,但部分客户端会识别)
- Go 中用
http.Redirect(w, r, "https://"+r.Host+r.URL.String(), http.StatusMovedPermanently),并在重定向前手动加 HSTS 头 - Cloudflare、AWS ALB 等托管服务可能自动剥离或覆盖 HSTS 头,需在控制台开启“安全头透传”或检查响应原始 header
- 测试是否生效:访问 HTTPS 页面后,打开 Chrome DevTools → Application → Clear storage → 刷新,再看响应 header 和地址栏锁图标是否稳定
真正容易被忽略的是:HSTS 缓存由浏览器维护,无法从服务端清除。配错 max-age 或误加 preload 后,可能要等数月才能失效。上线前务必用小范围 max-age=300 验证完整链路。


















