加一行安全响应头,差点把站点搞挂

  • 建站笔记

起因:几个安全响应头,想一次管全站

我在公司做办公室维护,平时管打印机、修电脑,公司内部那几个系统的日常维护也归我。自己的这个小站是下班以后折腾出来的,配置改得越多,胆子反而越小——踩过的坑基本都是一个套路:改的时候觉得没问题,出问题的时候又找不到原因。

前段时间我给站点补了几个安全响应头。想法很直白:页面不希望被别人的 iframe 嵌进去,浏览器别自己去猜文件类型,Referer 不要把完整地址到处带给外站,再加上一点基础的传输安全要求。这些东西不用改页面代码,写在网站服务器配置的最外层就行,一次生效全站。

写完我特地抓了一次响应头,首页有、文章页也有,挺满意,觉得自己这回总算干了件正经事。确认的办法也不复杂:打开浏览器的开发者工具,在网络面板里点开首页那次请求,看响应头那一段,三行都在。我当时还把截图存了下来,想着以后万一有人问起来,好歹能翻出来给人看。

标题里说"差点把站点搞挂",其实是句气话。站点一天都没挂,访问一直好好的。真正让我不踏实的是:我以为设置生效了,它其实早就没了。

后来加了两行缓存,功能上挑不出毛病

过了几天,我发现静态资源在浏览器里老是重新下载,就想着给它们设个缓存时间。做法很常规:在针对某个目录的配置块里加了两行,让图片这类不常变的东西在浏览器里多留几天。改完刷新页面,资源正常加载,缓存时间也对得上,一切看起来都很顺。

顺手说一句,我加的是那种不常改的东西所在的目录,图片、字体这一类。这些文件内容基本不动,让浏览器多留几天,回访的时候就不用重新下一遍。

我当时还在心里夸了自己一句,说这配置挺干净。

它不是叠加,是替换

问题是在我给站点写了个自动检查脚本之后浮出来的。脚本会把几个典型地址都请求一遍,把响应头打出来对照。跑完我发现,首页的安全头都在,可那几个我单独设过缓存的目录,安全头一个不剩。

我一开始以为是脚本写错了,后来手工又抓了一次,确认不是我眼花。

这类服务器软件的规则是:只要在更具体的配置块里出现了一次"添加响应头"这个指令,这个块就不再继承外层的任何响应头。它不是叠加,是替换。

也就是说,我为了设缓存顺手加的那一行,等于替那几个目录宣布"外层的头跟我没关系了"。缓存设对了,安全头全丢了,配置语法完全合法,服务也不会报任何错。

为什么这事特别难发现

页面打开一切正常,功能没有任何异常,没有白屏,也没有 500 报错。安全头缺失不会弹提示,控制台也不会说话。对访客来说,网站跟之前一模一样。

只有真的去抓一次响应头,才看得到那几行不见了。而且抓的位置还得对——你要访问的是那几个目录下的地址,光看首页看不出来。所以我后来把检查脚本改了,每一类路径都单独抓一次,不再只抓首页就收工。

那个脚本本身写得很土:拿几个地址挨个发请求,把状态码和响应头按行打出来,再跟我手写的一份清单对一遍。它不懂什么该有、什么不该有,全凭我列的那几行。也正因为清单里每一类路径都占了一行,这几条头不见了才被我撞上——要是清单里只有首页,这事估计还得再躺很久。

三种改法,我挑了最省事的

搞清楚原因之后,路子其实很清楚了:

我最后选了第三种。站点不大,安全头本来就该全站统一,放外层最省心;目录块只管缓存、跳转这些自家的事,谁也不越界。下面两段是"看着没问题"和"实际更稳"的区别,域名和路径都换成了通用写法。

第二种改法我也试过一小会儿,很快就放弃了。一旦决定"局部把头像重新写全",就等于把同一份清单在两三个地方各抄一遍;哪天要调整其中一条,得记得每一处都改。抄漏一处不会报错,只会继续静默失效——这正是我一开始掉进去的那个坑。

容易出问题的写法:外层有安全头,目录块里又冒出一个添加头的指令。

server {
    server_name example.com;

    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    location /static/ {
        # 就是这一行,让上面三行在这条路径下全部失效
        add_header Cache-Control "max-age=604800";
    }
}

改完之后的写法:目录块只管缓存,不出现任何响应头指令。

server {
    server_name example.com;

    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    location /static/ {
        expires 7d;
        root /var/www/example/;
    }
}

改完再抓一次,几个路径下的头就都回来了。就这么两行的事,前后折腾了我一个晚上。

顺手记下的一条经验

配置的"继承"和"覆盖"规则,每个软件都不一样,而且往往是静默生效的——不报错,不警告,只是悄悄地变了。它不会在你敲回车的时候拦住你问一句"你确定吗",它只会照你说的做。

所以我现在遇到"某个设置有的页面有效、有的页面没有",第一反应就是:是不是被更局部的配置顶掉了。从配置的层级往上找,多半能对上号。

这种"有的有、有的没有"其实还有别的样子:某个设置只在某一个子目录里不起作用,或者同样的配置在新机器上生效、旧机器上没动静。看着像玄学,往上翻两三层配置,通常就能看到某处藏着一次局部覆盖。

这类问题的通用解法,说到底就一句话:加完之后,去实际抓一次结果。别盯着配置文件想"应该没问题"——配置文件是我写的,里面装的是我以为的规则;抓回来的响应头,才是服务器实际干的事。


本文写于 2026-09-13,记录的是当时的情况与做法,未必适用于所有环境,仅供参考。

← 返回文章列表