生产环境必须关闭 display_Errors = off,开启 log_errors = on 并指定安全日志路径;catch 中禁止输出异常详情,只记录日志并返回通用提示;需验证配置生效、过滤敏感信息、保障日志安全。

错误信息里为什么总冒出数据库密码和文件路径
php 默认把 error_reporting 设成 E_ALL,又开着 display_errors = On,一出错就直接把堆栈、变量值、完整文件路径甚至数据库连接字符串全打在页面上。攻击者刷新几次 500 页面,就能凑出你的 config.php 路径和 $db_password 值。
关键不是“关掉错误”,而是“让错误只去该去的地方”:
-
display_errors = Off(必须关,生产环境绝对不许开) -
log_errors = On(错误写进日志,而不是吐给浏览器) -
error_log = /var/log/php/error.log(指定日志路径,确保 PHP 进程有写权限) - 别用
ini_set('display_errors', '1')在代码里临时打开——它会覆盖配置,且容易被遗忘
try-catch 捕获异常后,别再 echo $e->getMessage()
很多人以为“我用了 try-catch 就安全了”,结果在 catch 里写 echo $e->getMessage() 或 var_dump($e),照样把敏感信息原样输出。比如 pdo 异常的 getMessage() 会包含 sql 语句和参数值,getTraceAsString() 更是完整堆栈。
正确做法是:只记录原始异常,返回通用提示:
立即学习“PHP免费学习笔记(深入)”;
- 记录日志用
error_log($e->__toString(), 4)或写入文件(注意避免日志注入) - 前端只显示类似
"操作失败,请稍后重试"的静态文案 - 如果必须区分错误类型,用自定义错误码(如
ERR_LOGIN_FAILED),而非暴露异常细节 - 别在
catch里调用debug_print_backtrace()—— 它专为开发设计,上线即危险
php.ini 和 .htaccess 里的 display_errors 优先级容易搞反
很多人改了 php.ini 却发现错误还在页面上显示,其实是被 .htaccess 或虚拟主机配置覆盖了。apache 下 php_flag display_errors off 是运行时指令,会覆盖 php.ini;nginx 则靠 fastcgi_param PHP_VALUE "display_errors=Off" 控制,漏配就失效。
验证是否真正关闭:
- 写个测试脚本:
<?php trigger_error('test error', E_USER_WARNING); ?> - 访问它,页面空白(或只显示白屏)才算成功;如果看到黄色警告,说明某处仍开着
display_errors - 检查实际生效配置:
phpinfo()里找display_errors行,看 “Local Value” 是否为Off
自定义错误处理器里别直接输出 $_SERVER 或 $_REQUEST
有人为了“更友好”写自定义 set_error_handler,结果在回调里打印 $_SERVER['REQUEST_URI'] 或 print_r($_REQUEST),等于把 GET/POST 全部参数、请求头、服务器路径一股脑倒出来。特别是当用户传入恶意 ?debug=1 参数时,风险陡增。
真要调试,只记必要字段:
- 记录
$_SERVER['REQUEST_METHOD']和$_SERVER['SCRIPT_NAME'](不带参数) - 过滤
$_REQUEST:用array_keys($_REQUEST)看有哪些键,但绝不 dump 值 - 敏感键名如
password、Token、api_key必须从日志中屏蔽(可用preg_replace处理日志字符串) - 别在 handler 里调用
error_log(print_r($GLOBALS, true), ...)——$GLOBALS包含全部变量,包括已连接的数据库句柄和密钥
最麻烦的从来不是怎么关错误,而是关完之后没人定期检查日志权限、日志轮转策略和错误分类规则。线上日志文件如果可被 Web 访问,或者保留了半年没清理,那关掉 display_errors 也白搭。