PHP 500 错误定位四步:日志、开关、堆栈、复现

来源:互联网 时间:2026-09-04

500 和 502 的差别,一句话说清:502 是 Nginx 拿不到上游响应,500 是 PHP 自己跑着跑着崩了,然后把这个错误码一路传回浏览器。所以 500 的现场一定在 PHP 侧,去 Nginx 的 error.log 里翻是找不到的。

第一步,确认错误到底出在哪一层。curl 拿状态码只是看到表象,真正的判断依据是响应头里有没有 X-Powered-By 这类 PHP 打的标记,以及 Nginx error.log 里有没有对应的 upstream 报错。两边都没有,那基本就是 PHP 层自己返回了 500。

第二步,把错误显示开关打开。生产环境的 display_errors 默认是 Off,这是对的,不能让访客看到你的文件路径和报错细节。排查时临时改 php.ini 或者用 ini_set 打开,同时确认 log_errors 是 On,错误会写进 error_log 指定的文件。

这里有个细节值得注意:FPM 池配置里可以用 php_admin_value 覆盖 php.ini 的设置,而且 php_admin_value 的优先级更高、无法被脚本里的 ini_set 覆盖。如果你在 php.ini 里改了半天没反应,去池配置里看看是不是被 php_admin_value 顶掉了。

第三步,从日志里定位到具体文件和行号。PHP 的报错格式很规整,Fatal error 后面跟着文件路径和行号,再后面是出错的函数调用。有行号就好办了,直接打开文件看那一行在干什么——绝大多数情况是调用了不存在的函数、数组下标越界、或者 require 了一个不存在的文件。

<?php
// 500 排查期临时诊断脚本:只在排障时放上去,用完立刻删
// 放到网站根目录,浏览器访问 diagnose.php 查看真实报错

// 打开全部错误输出
error_reporting(E_ALL);
ini_set('display_errors', '1');
ini_set('log_errors', '1');
ini_set('error_log', __DIR__ . '/php_error.log');

// 打印当前生效的关键配置,确认有没有被 php_admin_value 覆盖
$keys = ['display_errors', 'log_errors', 'error_log',
'memory_limit', 'max_execution_time', 'error_reporting'];
foreach ($keys as $k) {
printf("%-22s = %s\n", $k, var_export(ini_get($k), true));
}

// 记录当前加载的配置文件路径,避免改错文件
echo "Loaded ini: " . php_ini_loaded_file() . "\n";

// 故意触发一个错误,验证错误是否真的能被看见
trigger_error("diagnose: 这是一条测试错误", E_USER_WARNING);

第四步,做最小复现。日志告诉你哪一行错了,但不一定告诉你为什么错。把那段逻辑抽出来,写个十行的独立脚本单独跑,能复现就说明问题在逻辑本身,复现不了就要考虑是上下文的差异——变量值、数据库内容、文件权限,这些都可能导致同样的代码在不同环境下表现不同。

踩过一次坑之后我养成个习惯:诊断脚本用完当场删掉。这类文件留在根目录上,等于把服务器路径、PHP 版本、配置细节全亮给外人看,被扫描器抓到就是白送的信息。排查完记得把 display_errors 也改回 Off。

还有一类 500 查起来特别费劲,因为它只在特定请求下出现——比如某个插件在处理特定数据时才崩。这种时候别硬猜,去翻 PHP 的慢日志和 FPM 日志,把出问题的请求特征抓出来,再用同样的参数去复现。没有复现路径的排查,基本等于碰运气。

 

相关文章

标签:

A5创业网 版权所有