go的http.server不自动启用hsts,需手动在https响应头中设置strict-transport-security,推荐用w.header().set()并校验x-forwarded-proto或r.tls,max-age应灰度提升至31536000,且预加载需满足includesubdomains、preload及根域https直连等硬性条件。

Go标准库http.Server如何启用HSTS
Go的http.Server本身不自动加HSTS头,必须手动在响应中写入Strict-Transport-Security。最稳妥的做法是在所有HTTPS请求的响应头里统一设置,而不是依赖中间件逻辑判断是否走HTTPS——因为一旦TLS终止在反向代理(如Nginx)后,r.TLS可能为nil,导致漏设。
- 永远用
w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload"),不要用Add(),避免重复添加 - 确保只在HTTPS请求中设置:检查
r.TLS != nil或更可靠地依赖X-Forwarded-Proto: https(如果你的负载均衡器设置了它) -
max-age建议从300(5分钟)开始灰度,确认全站HTTPS稳定后再升到31536000(1年)
使用gorilla/handlers时HSTS配置陷阱
很多人直接用handlers.CORS()或handlers.CompressHandler,但gorilla/handlers没有内置HSTS支持。它的SecureHeaders中间件(已归档)也不再维护,强行用会漏掉preload或includeSubDomains等关键参数。
- 别依赖第三方中间件“自动”加HSTS;自己写一个轻量中间件更可控
- 如果用了
handlers.ProxyHeaders,记得它只解析X-Forwarded-For和X-Forwarded-Proto,要配合检查r.Header.Get("X-Forwarded-Proto") == "https" - 注意
gorilla/handlersv1.5+对Header写入顺序无保证,HSTS头必须在任何WriteHeader调用前设置
HSTS预加载(preload)上线前必须验证的三件事
提交到hstspreload.org不是加个头就完事。Go服务返回的响应必须满足硬性条件,否则会被拒绝或后续被踢出列表。
- 响应头必须包含
includeSubDomains和preload,且max-age≥31536000 - 根域名(如
example.com)必须能通过HTTPS访问,并返回相同HSTS头——子域名(如api.example.com)单独设置没用 - 确保没有HTTP重定向到HTTPS的“跳转链”,hstspreload要求
http://example.com/必须直接返回301到https://example.com/,且该301响应也带HSTS头(这点Go默认不满足,需额外处理)
为什么http.ListenAndServeTLS不等于HSTS生效
启动HTTPS服务只是传输层加密,和HSTS完全无关。HSTS是浏览器策略,靠响应头驱动。哪怕你用ListenAndServeTLS跑得再稳,只要没发那个头,浏览器下次仍可能走HTTP。
立即学习“go语言免费学习笔记(深入)”;
-
ListenAndServeTLS失败时会panic,但HSTS缺失不会报错,容易被忽略 - 本地开发用
self-signed cert时,浏览器通常不执行HSTS(因证书不受信),测试要用真实域名+Let’s Encrypt证书 - 某些CDN(如Cloudflare)默认开启HSTS,可能掩盖Go服务本身的配置缺失——关掉CDN再测一次才准
真正难的不是加那行Set,而是确保每个路径、每个代理层级、每次重定向都透出正确的头。少一处,HSTS策略就断一环。










