主题的 404 页被 nginx 吃掉了:fastcgi_intercept_errors 的坑-第三维度MokIII

现象:主题明明有 404 页,访客看到的却是 nginx 的默认页

WordPress 主题通常会渲染一个像样的 404 页面(带导航、搜索框、"返回首页")。但我这个站上,故意访问一个不存在的地址时,返回的是 nginx 那个极简的默认 404 页。

量一下就很明显:

curl -s -o /dev/null -w '%{http_code} %{size_download} B\n' https://www.example.com/不存在的地址

我这边实测:404 / 146 B。而主题渲染出来的 404 页是三万多字节(写这篇时实测 35672 B,它会随页面内容浮动)。差了 200 多倍——说明主题的页面根本没被送到浏览器。

一、原因:fastcgi_intercept_errors on 加一条指向不存在文件的 error_page

链条是这样的:

  1. 全局 nginx.conf 里开了 fastcgi_intercept_errors on;
  2. 站点 vhost 里有一行 error_page 404 /404.html;
  3. 但站点根目录下并没有 404.html 这个文件

于是当 WordPress(PHP)返回 404 时:

  • fastcgi_intercept_errors on 让 nginx 接管 PHP 返回的错误码,用自己的错误页;
  • 它去找 /404.html,没找到;
  • 最后回退到 nginx 内置的 146 B 默认页。

结果:PHP 那边辛苦渲染的那三万多字节的主题 404 页,被整体替换掉了。

判断方法:看响应里有没有 WordPress 的特征。

curl -sD - -o /dev/null https://www.example.com/不存在的地址 | grep -iE 'HTTP/|x-powered|link|set-cookie'

如果只有 nginx 的头、没有任何 WordPress 痕迹,就说明请求的结果被 nginx 换掉了。

二、修复:让 PHP 的 404 原样透传

在站点级配置里加一行(只影响本站):

fastcgi_intercept_errors off;

这样 PHP 返回什么状态码和页面,nginx 就原样送给浏览器。

三、代价必须知道

关掉它之后,PHP 的 5xx 错误页也会原样透传——也就是说,如果 PHP 出错,用户可能看到白页或者报错内容,而不是 nginx 友好的错误页。

这是可以接受的交换:主题的 404 页是有设计、有导航的;PHP 5xx 本来就不该出现,出现时能看到真实报错反而有利于排障。 如果你的站点更在意"错误页要好看",可以换个思路:真的在站点根目录放一个 404.html,让 error_page 有东西可指。

另外注意:nginx 自身产生的静态 404(比如图片不存在)仍然会走 error_page,这条修改只影响 FastCGI(PHP)返回的错误。

四、排序问题

fastcgi_intercept_errors off; 是纯 server 指令,不含 rewrite,所以它不像那些带 if 的规则会被 break 干扰。但仍建议把它放到静态缓存规则之前的独立文件里,保持"先安全、后缓存"的顺序,便于日后维护。

五、验证

# 内容长度应该回到主题渲染的量级(几 KB 到几十 KB)
curl -s -o /dev/null -w '%{http_code} %{size_download} B\n' https://www.example.com/不存在的地址

# 页面里应能看到主题的导航/搜索等元素
curl -s https://www.example.com/不存在的地址 | grep -c 'site-navbar|search'

六、回滚

删掉那一行(或整个独立配置文件)→ nginx -t && nginx -s reload。回滚后立刻恢复成 nginx 默认页。


一句话总结:fastcgi_intercept_errors on 加上一个指向不存在文件的 error_page,会让主题精心渲染的 404 页被 nginx 的默认页静默替换。加一行 fastcgi_intercept_errors off; 就能让 PHP 的响应原样透传——代价是 PHP 5xx 也会露出真实内容。