根目录那个 sitemap.xml 不是谁手写的,是我自己部署的 sitemap.php 写出去的缓存。发现这一点之前,我刚为它写完一个"动态接管"插件——从动笔到回滚,纯白干。
起因:两个 sitemap 并存
那天晚上排查链接结构,发现站点挂着两份站点地图:自建的 /sitemap.xml,和 WordPress 自带的 /wp-sitemap.xml。后者还在往外提交作者页。作者页早就设了 noindex,noindex 的页面再进 sitemap,就是两处规则各自都对、合起来打架——跟之前写的一页两个 robots 标签是同一类病。方案定了:把 wp-sitemap 那套 301 归拢到 /sitemap.xml。
动手前我先看了一眼 /sitemap.xml 本身:
$ curl -sI https://站点/sitemap.xml
ETag: "…"
Accept-Ranges: bytes
Content-Length: 1041
三个头全是 nginx 静态文件的特征。再 ls 站点根目录,sitemap.xml 确实躺在那儿。两个证据指向同一个结论:手动维护的静态文件。
我当时想得很顺:静态文件,意味着每发一篇文章都得手动往 XML 里加 URL。写个 mu-plugin 动态生成,以后就不用管了。
部署完,大小纹丝不动
插件写完、部署,一切顺利。curl 一验,坏了:响应头还是那三件套,内容还是 1041 字节,跟部署前一模一样。

第一反应是改动没生效,在插件那边来回查。其实正确动作是停下来问一句:这个请求到底走的哪条路?我没问,一头扎在插件里。
最后捅破窗户纸的动作特别小,把根目录的 sitemap.xml 挪走,再请求一次:
$ mv sitemap.xml /tmp/ # 把缓存挪走
$ curl -s https://站点/sitemap.xml | head -3 # 新内容立刻出来
就这一下,真相现形。
真相:缓存文件截了所有的胡
这个 sitemap.php 是我当天傍晚亲手部署的:查库、拼 XML,生成完写回根目录当缓存。nginx 伪静态里还有一行:
location = /sitemap.xml {
try_files $uri /sitemap.php?$args;
}
意思是:根目录有 sitemap.xml 就直接吐静态文件,没有才轮到 sitemap.php。ETag、Accept-Ranges 这些头是 nginx 加的——它不关心文件是手写的还是 PHP 写的,文件存在就按静态伺候。
所以"1 小时刷新"从来不会自动发生:缓存文件一存在,nginx 就把它吐回去,PHP 根本不执行,地图一直挂着旧内容,直到有人 rm 掉缓存。当天发第一篇文章时就踩过一次:发完不更新,rm -f sitemap.xml 才重新生成,这一步还写进了校验脚本。几个小时后,我把同一套机制认成了手写文件。
更尴尬的是我的接管插件:缓存文件在,请求永远到不了 PHP 层,插件一行代码都没跑过。两套逻辑抢一个入口,除了混淆没有任何收益。

动手前先 grep 一遍伪静态
回滚很简单:接管插件删掉,保留 sitemap.php 和 try_files。收敛插件只管它该管的——关掉 WP 自带地图,wp-sitemap 全部 301 到 /sitemap.xml,复验 21/21。
真正该做的动作其实只有这一行,两分钟出结果:
grep -rn 'sitemap' /www/server/panel/vhost/rewrite/
伪静态里谁接住了哪个 URL,一目了然。这行命令我最后才跑,按理它应该在写插件之前。
回头看,两个教训。响应头的静态特征只证明"nginx 直接吐的",不证明"人手写的",缓存产物会带一模一样的头。动一个 URL 之前先确认归属——nginx 静态命中、rewrite 到独立脚本、应用路由,三层可能同时存在。nginx 执行顺序的坑我不是第一次栽,之前写过 return 写在 server 层会绕过所有 location,这次是 try_files 把插件整个跳过。比起相信记忆,还是 grep 两分钟靠谱。