Nginx 把 PHP-FPM 接口的 500 JSON 换成 HTML 错误页
PHP 接口已经返回状态码 500 和 JSON 错误内容,客户端收到的却是站点统一的 HTML 错误页。正常请求仍是 JSON,只有失败请求被替换时,应核对处理该接口的 fastcgi_intercept_errors 和 error_page,而不是给 HTML 强行加一个 JSON 类型。
PHP 接口已经返回状态码 500 和 JSON 错误内容,客户端收到的却是站点统一的 HTML 错误页。正常请求仍是 JSON,只有失败请求被替换时,应核对处理该接口的 fastcgi_intercept_errors 和 error_page,而不是给 HTML 强行加一个 JSON 类型。
下面适用于 Nginx 通过 FastCGI 连接 PHP-FPM、应用本身能够生成所需 JSON 的场景。如果后端实际使用 proxy_pass,对应的是 HTTP 代理模块;修改 FastCGI 指令不会影响那条请求。Nginx 自己产生的网关错误也不等于应用已经返回了 JSON。
先确认替换发生在哪一层
保留一个能够稳定触发问题的受控请求,核对状态码、Content-Type 和响应正文,并通过应用日志确认 PHP 已生成相应结果。避免为了制造错误停止生产数据库,也不要反复重放会创建订单或写入数据的请求。
检查实际加载的配置及其 include:是否在 http、server 或最终处理请求的 location 中开启了错误拦截,是否存在 error_page 500 指向静态页面。FastCGI 错误拦截默认为 off;开启后,符合条件的上游响应才会交给 Nginx 的错误页处理。
尤其注意应用的前端控制器。/apis/item 可能先匹配一个普通路径,再由 try_files 或 rewrite 内部转到 /index.php。只在最初的 location 写 off,而最终 FastCGI location 仍开启拦截,不能保证保留响应。
如果应用日志表明它本来就输出了 HTML,关闭拦截也只会透传 HTML。此时应修应用的异常处理和响应格式。把 Content-Type 改成 application/json 不会把 HTML 转换成合法 JSON。
在实际 FastCGI 处理位置关闭拦截
若该 location 只处理需要保留上游响应的 API,保留原有转发、参数和安全限制,只增加或调整:
fastcgi_intercept_errors off;
这条指令针对 FastCGI 返回的响应,不会把错误状态改成 200。也不必因此删除整个站点的 error_page 配置,普通网页可以继续使用既有的错误页策略。
如果 API 与普通页面都内部转到同一个 PHP location,而两者要求不同,应先分清入口路由,不能在共享位置关闭拦截后假定其他页面不受影响。以下只展示“全部 API 均由固定 index.php 处理”的独立入口结构,放在已有 server 内;路径、socket 和参数文件必须按部署替换:
location = /apis {
include /etc/nginx/fastcgi_params;
fastcgi_param SCRIPT_FILENAME /srv/app/public/index.php;
fastcgi_pass unix:/run/php/app-fpm.sock;
fastcgi_intercept_errors off;
}
location ^~ /apis/ {
include /etc/nginx/fastcgi_params;
fastcgi_param SCRIPT_FILENAME /srv/app/public/index.php;
fastcgi_pass unix:/run/php/app-fpm.sock;
fastcgi_intercept_errors off;
}
这是相关 location 的结构示例,不是替换整站配置。前提是应用确实以这个固定脚本接收这些 API 路径,并且 FPM 能看到该脚本路径。不能把请求 URI 随意拼为可执行脚本,也不要把 socket 示例改成向公网开放的 FPM 端口。
检查 include 文件提供了当前应用需要的 FastCGI 参数,尤其是原始请求 URI、查询参数、方法、正文类型与长度,以及 HTTPS 等原有设置。它不应再提供冲突的 SCRIPT_FILENAME;不同发行版的参数文件内容可能不同,不能只凭文件名判断。当前层定义 fastcgi_param 后还需注意参数继承,保留原来必须传递的项。
精确匹配 /apis 与前缀 /apis/ 分开,避免把 /apis-other 也算作接口。这里的 ^~ 会影响正则 location 的选择,因此必须把 API 所需的访问控制、限流等规则保留下来,不能绕过原先正则块承担的限制。普通页面的处理位置保持原有配置。
验收错误格式,也验收普通页面
修改前保存配置与 include 文件。在受控环境用实际部署的 Nginx 校验配置,再按原服务管理流程加载;配置可解析只证明语法成立,不能证明 PHP 路由和参数正确。
验证 /apis、具体子路径、带查询参数的请求及应用实际使用的方法:成功请求仍正常,受控的应用错误保留原状态码和可解析的 JSON,普通网页仍按原策略显示错误页。错误 JSON 中不应向客户端泄露堆栈、凭据或数据库内部信息,这需要应用自行处理。
还应单独确认上游不可达等网关故障符合接口约定。关闭 fastcgi_intercept_errors 不会凭空生成应用响应,也不会把所有 Nginx 错误都转成 JSON;若需要统一网关错误格式,应另行设计,不以一次应用 500 验收代替。
若出现路由或参数回归,恢复本次 location 与参数文件修改,重新校验并按原方式加载。保留失败请求与日志作对照,不需要删除应用数据、关闭所有缓存或移除全站错误页。


