
现象:同一个响应头出现两次
站点刚接上 CDN 之后,我用 curl -I 检查响应头,发现 Strict-Transport-Security 出现了两行:
Strict-Transport-Security: max-age=15552000
Strict-Transport-Security: max-age=31536000
这不是"更安全",这是冲突。
一、HSTS 是什么,为什么重复会出问题
HSTS(HTTP Strict Transport Security)是一句"命令":告诉浏览器"以后访问我这个域名,只准用 HTTPS"。浏览器收到后,会在有效期内把该域名的 http 请求在本地直接改写成 https,不再发出明文请求。
它的效力由 max-age 决定。而 RFC 6797 明确规定:当响应中出现多个 HSTS 头时,浏览器只采用第一个,忽略其余。
问题就在这——哪个"第一个"取决于头部的排列顺序,而你可能毫不知情地:
- 以为生效的是"A 侧那条长有效期",实际用的是"B 侧那条短的";
- 或者写
includeSubDomains的那条被忽略,子域其实没被覆盖。
越想通过"两边都发"来加强,越可能拿到最弱的那条。
二、怎么查
# -I 只看响应头
curl -sI https://www.example.com/ | grep -i 'strict-transport-security'
# 想看原始头部顺序,用 -D
curl -sD - -o /dev/null https://www.example.com/ | grep -i 'strict-transport'
计数很重要:出现 2 行就是重复了。
三、原则:只让一侧发
在"源站 + CDN"的结构里,这个头两条路径都会加:
- 源站 nginx 配置里写的
add_header Strict-Transport-Security ...; - CDN 侧(很多厂商在控制台里默认打开"HTTPS 强制"并会自动下发 HSTS)。
正确做法是二选一:
- 让 CDN 发(推荐用于接入 CDN 的站):源站把 HSTS 注释/删掉。这样只有一条,且由离用户更近的一侧下发;
- 让源站发:那就去 CDN 控制台关掉它的 HSTS。
我选的是前者——源站那段 add_header 注释掉,备份好原文件。
四、配置示例
# 源站在已接 CDN 的情况下不再下发 HSTS(避免与 CDN 重复,RFC 6797 只取第一个)
#add_header Strict-Transport-Security "max-age=31536000";
注意几点:
- 改之前备份 vhost(面板重新生成站点配置会覆盖你的手改,所以改动最好放在独立文件里,或者记清备份路径);
always参数的作用是"无论状态码如何都加这个头",去掉时一并注意;- 改完
nginx -t && nginx -s reload。
五、验证与一个容易忽略的缓存问题
curl -sI https://www.example.com/ | grep -c -i 'strict-transport-security' # 期望 1
如果还是 2,先别怀疑配置——CDN 和浏览器都可能缓存了响应头:
- CDN 侧刷新一下缓存(一般在控制台有"刷新缓存"按钮,或带随机参数请求);
- 浏览器自己的 HSTS 缓存是独立的(在
chrome://net-internals/#hsts里能看和清),但它只影响你的浏览器行为,不影响服务端返回什么。
六、关于 preload:想清楚再上
HSTS preload 是把自己加进浏览器内置列表,让首次访问就强制 HTTPS。它几乎没有回头路——申请容易,移除要等浏览器版本更新,期间你的域名在用户浏览器里就是"只能用 https"。
建议:站点至少稳定运行半年、所有子域都确认支持 HTTPS 之后再考虑,而且先用短 max-age 试一段时间。
七、回滚
取消注释即可恢复由源站下发。但回滚时要同时确认 CDN 那一侧是否也开着——两侧都开就是你一开始遇到的问题。
一句话总结:HSTS 出现两次时,浏览器只认第一个,而你没法控制哪个在前。接入 CDN 后第一件事就是检查这个头,只保留一侧下发;includeSubDomains 和 preload 都要在确认影响范围之后再加。

评论 (0)