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

推荐订阅源

aimingoo的专栏
aimingoo的专栏
腾讯CDC
Y
Y Combinator Blog
L
LangChain Blog
B
Blog
U
Unit 42
P
Proofpoint News Feed
G
Google Developers Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - 【当耐特】
WordPress大学
WordPress大学
月光博客
月光博客
Vercel News
Vercel News
雷峰网
雷峰网
T
The Blog of Author Tim Ferriss
MyScale Blog
MyScale Blog
大猫的无限游戏
大猫的无限游戏
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
酷 壳 – CoolShell
酷 壳 – CoolShell
Blog — PlanetScale
Blog — PlanetScale
博客园 - 司徒正美
云风的 BLOG
云风的 BLOG
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园 - 叶小钗

蛋蛋之家

Halo 博客晚间卡顿排查记:从"玄学卡顿"到"真凶落网" 我用这个开源项目,把 AI 绘画提示词变成了"代码"——awesome-gpt-image-2 深度体验报告 群晖部署 LX Music Server:浏览器就是你的音乐播放器【详细版】 500道Word题库太痛苦?于是我写了个智能学习平台 别用一个人的奔跑,耗尽两个人的余生——《你凭什么认为我会一直喜欢你呢》 博客评论自动填充新方案:兼容 Vue/React 响应式绑定与跨架构填充逻辑 2026 生产级指南:Halo 2.x + PostgreSQL 自动化部署与性能优化手册 从 Typecho 转向 Halo 2.x:全能型 Nginx 架构与 SEO 无损迁移指南 Typecho 迁移 Halo 完整教程:数据库视图解决表前缀 + 自动提取封面图 + 去重检测 我的 CodeBuddy 装备库:218个技能全公开 + 使用心得 为博客添加自定义评论表情 Comment2Bark:Typecho评论推送到Bark插件 又拍云插件 UpyunFile v1.2 更新:修复 Typecho 1.3.0 兼容性问题 手把手修复RSS 403:从PHP警告到Cloudflare拦截的全链路排查 网站被镜像怎么办?2026年反镜像攻防实战指南 VLESS + Reality + Vision:2026 极致隐蔽节点搭建全解析 给OpenList和Alist加上液态玻璃效果,颜值直接拉满! 网页截图瞬间“赛博飞升”?手把手教你根治 Windows HDR 截图发白 群晖 NAS 影音终极方案:Docker Rclone + OpenList 完美挂载 Plex 全攻略 一个简洁高效的SVG图标库 告别重复劳动!一键填充评论的神奇书签脚本 - 蛋蛋之家 解锁思源笔记的隐藏力量:siyuan-patch 深度体验与技术实现 Oracle Cloud 自救指南:旧手机丢失后如何绕过 MFA 重新登录【更新2026年最新政策】 本站已加入“萌国ICP备案”联萌!😄 Excel 月报自动化指南:告别重复劳动,让数据主动为你服务 梦呓:为异地女友准备的2周年惊喜礼物 Typecho 多吉云插件优化:更灵活的存储目录配置 甲骨文云硬盘扩容指南:从50G到200G的“免费午餐”怎么吃? ⏰ 时间同步:当你的服务器开始“穿越”时,如何优雅地把它拉回现实 Typecho 又拍云文件管理插件修改版使用指南:新增自定义目录结构功能
Nginx 又双叒给我挖坑了:HSTS 撞车 + 宝塔偷偷锁 Frame
吴蛋蛋 · 2026-06-14 · via 蛋蛋之家

环境: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 绿灯,配置保存不报错。响应头清清爽爽,强迫症舒服了。


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