惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

爱范儿
爱范儿
量子位
人人都是产品经理
人人都是产品经理
小众软件
小众软件
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - Franky
Recent Announcements
Recent Announcements
A
About on SuperTechFans
Microsoft Security Blog
Microsoft Security Blog
N
Netflix TechBlog - Medium
H
Help Net Security
博客园 - 三生石上(FineUI控件)
博客园 - 司徒正美
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
MyScale Blog
MyScale Blog
The Cloudflare Blog
S
SegmentFault 最新的问题
H
Hackread – Cybersecurity News, Data Breaches, AI and More
J
Java Code Geeks
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
P
Proofpoint News Feed
宝玉的分享
宝玉的分享
Vercel News
Vercel News

BlogFinder

日常漫步 Vol.24 之漫步前山河 - 雅余 周报 #1-聊聊本周的收获 - Edwin's Blog 我的OpenCode必装插件与Skill Write Something 掌中之物未必在掌握之中 · CRIVU PiliNara,一个更顺手的 PiliPlus 分支 「NekoEcho」:做一个必有回响的猫娘主题博客 2026-05 书影音总结 简化博客主题 - 安迪 我第一次发布 npm 包 拾花小记#45:中考前的二三事 – 小改学习志 黛西花园5月游 #18 枇杷又熟了的五月月报 一些奇奇怪怪的需求?word仿方正书版的几个小操作 - Xiobb's Blog 0419 御温泉之旅 修复了一些bug,网站基本上趋于稳定了 - 新锐博客 又回到四十年前 如何定义成功 迷鹿屋2026已重新上线 科技冰火两重天+一周回顾 ${title} 热度退了,我反而用得更深了-咕咚同学 我到底该不该换个域名? 随身WIFI折腾记 - 安迪 博客撰写体验提升——hexo pro插件 为什么不用相机把屏幕上的接关密码拍下来? 国清寺与天台山 – Ouroboros ★★★★☆《挽救计划》——久违的经济上行感 - Davidの3号基地 删除右键“打开方式”里多余选项 第三周刊_No.53|一切都会被支付两次
Nginx 又双叒给我挖坑了:HSTS 撞车 + 宝塔偷偷锁 Frame
吴蛋蛋 · 2026-06-14 · via BlogFinder

环境:Nginx 1.28.3 + 宝塔面板 + Halo 2.x + Cloudflare

把博客从 Typecho 搬到 Halo 2.x,Docker Compose 跑起来,反代配好,心想总算能歇了。

结果一接 Cloudflare,控制台开始疯狂报红。再一试跨站嵌入,直接给我拒了。两个坑连环爆,折腾了一晚上,记录一下,下次别再踩了。

动漫版头图,可爱吧
动漫版头图,可爱吧

第一坑:HSTS 重复输出,Cloudflare 和我各说各话

咋回事呢?

我习惯在 Nginx 里开 HSTS,强制走 HTTPS:

add_header Strict-Transport-Security "max-age=31536000" always;

接入 Cloudflare 后,浏览器开始骂街——Duplicate Header。

因为 Cloudflare 边缘节点本身就会统一添加 HSTS 头,回源时我再加一次,就撞车了。一个响应头出现两次,浏览器很懵:你俩到底听谁的?

我第一反应:上 if 啊!

这还不简单?判断一下有没有 Cloudflare 的特征头,没有再加 HSTS 呗:

# 直觉写法,当场翻车
if ($http_cf_ray = "") {
    add_header Strict-Transport-Security "max-age=31536000" always;
}

​nginx -t​ 直接甩脸:

[emerg] "add_header" directive is not allowed here

???不让我加?

后来才知道,Nginx 的 if 出了名的邪门,江湖人称 "If is Evil"。你想在 if 里塞 add_header​?门儿都没有。这玩意儿只能在 serverlocation 里裸奔,不能塞进 if​ 的怀抱。

解法:map 大法好

既然 if 靠不住,那就把逻辑抽到 http 块,用 map 做判断(注意:map 不能放在 server 块内部):

http {
    # 动态 HSTS 智能过滤映射
    # Nginx 1.7.5+ 支持:值为空字符串时 add_header 自动隐匿
    map $http_cf_ray $dynamic_hsts {
        default "";                    # 有 cf-ray?走 Cloudflare,HSTS 留空
        ""      "max-age=31536000";    # 没 cf-ray?直连源站,上 HSTS(有效期1年)
    }
    
    server {
        # ...
        add_header Strict-Transport-Security $dynamic_hsts always;
    }
}

妙处来了: Nginx 1.7.5+ 中,add_header​ 有个隐藏特性——值是空字符串 ""​ 时,这个头直接隐身,不会出现在响应里

所以走 Cloudflare 时 $dynamic_hsts""HSTS 自动消失;直连源站时输出 max-age=31536000,完美。if​ 你不行,我 map 上,照样搞定。


第二坑:宝塔偷偷给我加了 X-Frame-Options

又是咋回事呢?

我想搞跨站嵌入,结果浏览器死活不让:

拒绝在 Frame 中加载,因为响应头包含了 X-Frame-Options: SAMEORIGIN

我明明在反代里写了:

proxy_hide_header X-Frame-Options;

Halo 后端吐的头应该被抹掉了啊,这 SAMEORIGIN 从哪冒出来的?我一度怀疑人生,是不是 Halo 在跟我作对。

排查:宝塔是内鬼

顺着配置翻啊翻,发现宝塔面板自动生成了一堆扩展 conf(注意:wuqishi.com 替换为你的实际域名):

include /www/server/panel/vhost/nginx/extension/wuqishi.com/*.conf;
include /www/server/panel/vhost/nginx/generic/wuqishi.com/*.conf;

里面静静躺着:

add_header X-Frame-Options SAMEORIGIN;

好家伙,宝塔你背刺我!

关键认知proxy_hide_header 只能藏后端服务器吐的头,对 Nginx 自己配置里写的头完全没辙。宝塔全局一加,我的反代配置根本管不到,就像你在家锁了门,结果物业在小区大门又加了一道锁。

解法:不跟宝塔纠缠,直接上 CSP

去翻宝塔文件删了?太怂,而且面板一更新可能又刷回来,跟打地鼠似的。直接在反代入口写三重保险:

# 第一重:后端来的 X-Frame-Options,抹掉
proxy_hide_header X-Frame-Options;

# 第二重:宝塔全局加的?强行重置为空,浏览器看到空值直接忽略
add_header X-Frame-Options "" always;

# 第三重:终极杀招,CSP 的 frame-ancestors 优先级碾压 X-Frame-Options
# * 表示允许所有域名嵌入,如需限制改为 https://example.com
add_header Content-Security-Policy "frame-ancestors *" always;

原理: 现代浏览器里,CSP 的 frame-ancestors 指令优先级远远高于老古董 X-Frame-Options​。看到 frame-ancestors *​,浏览器当场无视 SAMEORIGIN,跨站嵌入一路绿灯。

* 意思是 "谁都能嵌",如果你只想让特定域名嵌,如改成 frame-ancestors https://wuqishi.com 就行。但我懒,先开全通,后面再收紧。


收工,总结一下

两个坑,都是响应头的小把戏,但排查起来很费神。最后靠 mapCSP 组合拳搞定,没动业务逻辑,纯粹是边缘层的斗智斗勇。

核心解法

一句话总结

HSTS 撞车

​map动态变量

有 Cloudflare 就隐身,直连再上 HSTS

宝塔锁 Frame

CSPframe-ancestors​

优先级碾压,不跟宝塔文件纠缠

nginx -t 绿灯,配置保存不报错。响应头清清爽爽,强迫症舒服了。


解决问题的乐趣就在于此,把流量在边缘层梳理得明明白白,爽。