yanchang
yanchang
发布于 2026-01-31 / 30 阅读
0
0

排错实录:Nginx 配置没写 HSTS,为什么浏览器死活强制跳转 HTTPS?

碎碎念

有些 Bug 真的是用来“修心”的。今天给我对象下载音乐,然后上传到我的oplist上,给我对象开一个账号,这样他就可以在线听歌了,花了九牛二胡之力,下载完了,突然发现我无法打开任何的http页面了,只能https,但是我有一个陈年BUG,那就是https无法上传(下一个博客修复)

折腾了大半天,把 Nginx 的文档翻烂了,甚至一度怀疑是不是自己手抖改坏了配置。结果最后发现,并不是我做错了什么,而是后端框架“太贴心”地帮我做了一些我并不需要的安全加固。

这件事再次教会我一个道理:永远不要太相信浏览器这种“智能化”过头的客户端。它会缓存、会自作主张、会为了用户体验掩盖底层逻辑。当一切陷入迷雾的时候,只有黑底白字的命令行(curl)和冰冷的日志才是最诚实的伙伴。

虽然浪费了几个小时,但看到 curl 输出那个 Header 的瞬间,那种破案的快感,大概就是我们还要继续写代码的原因吧。周末愉快,继续折腾。

正文内容

1. 问题起因:诡异的端口跳转

最近在折腾 HomeLab 服务器,我的 Halo 博客采用的是端口分离的部署模式:

  • 8090 端口:纯 HTTP,用于内网或者特定调试。

  • 8091 端口:HTTPS,对外提供安全访问。

原本两个端口一直相安无事。但就在今天,当我配置好 HTTPS 并成功访问了一次 https://...:8091 后,诡异的事情发生了:我的 8090 端口再也无法通过 HTTP 访问了。症状如下

8090(http服务)和8091(https服务)

但是,如果一直访问8090,那么ok没问题,如果一直访问8091那么也ok。

但是如果你访问完8091之后,再去访问8090,那么你会发现,浏览器会自动的把你当前的域名的http请求修改为https请求,无论你怎么设置浏览器都是无效的都会默认跳转为https导致无法访问http服务,现在请你思考为什么。

每当我在浏览器输入 http://yanchang.cc:8090,Chrome 和 Edge 都会瞬间将其强制重定向到 https://yanchang.cc:8090。 因为 8090 端口压根没配置 SSL,结果自然是连接失败(ERR_SSL_PROTOCOL_ERROR)。

2. 初步排查:薛定谔的缓存

第一反应肯定是浏览器的 HSTS (HTTP Strict Transport Security) 缓存作祟。 于是我熟练地打开 chrome://net-internals/#hsts,删除了我的域名策略。

  • 结果:删除后,http://...:8090 居然恢复正常了!

我以为问题解决了。然而,只要我手贱再去访问一次 https://...:8091,8090 端口立马又坏了,再次强制跳转 HTTPS。

奇怪的点在于: 我服务器上部署的其他服务(如 OpenList 等)也是类似的配置,却从来没有这个问题。为什么偏偏只有 Halo 这个服务“中毒”了?,并且中毒之后,会导致所有的http服务都无法正常访问了。

3. 深入挖掘:消失的配置代码

HSTS 的生效原理是服务器返回头中包含 Strict-Transport-Security。既然浏览器记住了,那肯定是服务器发了这个头。

我第一时间去检查 Nginx 的配置文件:

Bash

sudo grep -r "Strict-Transport-Security" /etc/nginx/

结果令人窒息:输出为空。 我的 Nginx 配置里干干净净,根本没有写这一行代码。既然我没配置,Nginx 凭什么自作主张给浏览器发 HSTS 头?

4. 真相大白:命令行显神威

既然肉眼看不出 Nginx 配置的问题,那就绕过浏览器,直接用 curl 看看服务器到底吐出了什么鬼东西。

我在终端执行了以下命令,检查 HTTPS 端口的响应头:

Bash

curl -I https://www.yanchang.cc:8091

输出结果让我恍然大悟:

YAML

HTTP/2 404 
server: nginx/1.24.0 (Ubuntu)
...
strict-transport-security: max-age=31536000  <-- 凶手就在这里!
...

分析结论: 虽然我的 Nginx 没写这行代码,但是 Nginx 作为一个反向代理,它的职责是把后端应用(Halo)的响应转发给客户端。 真相是:Halo 博客应用为了安全性,在代码层面默认开启了 HSTS,并加上了这个 Header。 Nginx 只是老实地把它转发给了浏览器。 浏览器收到这个“有效期一年(31536000秒)”的指令后,直接把整个域名(不管什么端口)拉入了 HTTPS 强制名单。这就解释了为什么其他服务没事(因为其他应用没发这个头),只有 Halo 出了问题。

5. 最终解决方案:Nginx 拦截与覆盖

既然轻易修改 Halo 的源码比较麻烦,最优雅的办法是在 Nginx 层面进行“拦截”。我们需要做两件事:

  1. 拦截:不让 Halo 发出的 HSTS 头传给浏览器。

  2. 解毒:主动发一个新的 HSTS 头(max-age=0),告诉浏览器“忘了之前的强制策略吧”。

修改 /etc/nginx/sites-enabled/halo.conf,在 location / 块中加入以下配置:

Nginx

location / {
    proxy_pass http://127.0.0.1:8090;
    
    # 1. 【屏蔽】强制隐藏后端应用(Halo)发来的 HSTS 头
    proxy_hide_header Strict-Transport-Security;

    # 2. 【覆盖】告诉浏览器立即清除 HSTS 缓存 (max-age=0)
    add_header Strict-Transport-Security "max-age=0" always;

    # 其他常规配置...
    proxy_set_header Host $host;
    # ...
}

保存并重载 Nginx (nginx -s reload)。

6. 验证

再次访问 HTTPS 端口(8091),浏览器接收到了 max-age=0 的指令,乖乖清除了缓存。 回头再去访问 HTTP 端口(8090),终于不再强制跳转了,问题彻底解决!

7. 总结

这次排错给我两个教训:

  1. 反向代理不仅是转发流量,也是转发 Header。如果后端应用“自作主张”加了某些安全头,前端 Nginx 是会照单全收的。

  2. 不要过度依赖浏览器的表现。浏览器会有缓存、会有各种策略干扰判断。在排查 HTTP 协议问题时,curl -I 永远比浏览器更诚实。


评论