生产环境必须关闭错误页面中的调试信息,否则会暴露文件路径、变量值、堆栈等敏感内容;需同步关闭PHP的display_errors和display_startup_errors、禁用FrankenPHP的--debug模式、限制Caddy的metrics/admin端点,并审查框架异常处理器是否兜底输出。

生产环境必须关闭错误页面中的调试信息,否则会直接暴露文件路径、变量值、堆栈、框架内部结构等敏感内容。这不是靠改一个 display_errors 就能解决的,而是 PHP 配置、FrankenPHP 运行时、Caddy 路由三者共同作用的结果。
确认并关闭 PHP 层级的错误输出
FrankenPHP 本身不接管 PHP 错误渲染逻辑,它只是运行 PHP —— 所以最终是否把 Fatal error 渲染成带堆栈的 HTML 页面,完全取决于 php.ini 的配置是否生效:
-
display_errors = Off(必须关,这是最直接控制“是否输出到浏览器”的开关) -
display_startup_errors = Off(防止 PHP 启动阶段的扩展加载失败被暴露) -
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT(线上推荐值,避免弃用警告干扰日志,也不该出现在页面上) - 检查
php.ini是否被正确加载:运行frankenphp php-cli -i | grep "Loaded Configuration File",编辑对应路径下的文件,保存后重启 FrankenPHP 进程
禁用 FrankenPHP 的 --debug 模式
--debug 参数不仅影响日志格式,还会让未捕获异常更早冒泡,并在响应头中注入 X-PHP-Exception;即使 PHP 层已关 display_errors,某些异常仍可能因该模式被增强输出:
- 确保启动命令中不含
--debug(例如不要用frankenphp serve --debug) - Docker 环境下检查是否误设了
FRANKENPHP_DEBUG=1环境变量 - 验证方式:触发一个语法错误(如在
public/index.php开头加asdf();),访问页面应返回空白或通用 500 响应体,而非带<pre class="brush:php;toolbar:false;"></pre>的堆栈
移除或限制 Caddy 的 metrics/admin 端点
这些端点本身不显示 PHP 错误,但它们是调试面的延伸入口,一旦暴露,攻击者可通过 /metrics 或 /admin 获取进程状态、内存使用、甚至热重载开关状态,间接辅助漏洞利用:
立即学习“PHP免费学习笔记(深入)”;
- 从
Caddyfile全局块中删除或注释掉metrics指令;若保留,限定为内网地址,如metrics 127.0.0.1:2019 - 删除或注释掉
{ admin }块;若必须保留管理接口,绑定地址必须严格限制,如admin 127.0.0.1:2019 - 检查
frankenphp.yaml中是否启用了features.metrics或features.admin,设为false
检查框架是否绕过 PHP 配置兜底输出
像 Laravel、Symfony、ThinkPHP 这类框架常在异常处理器中主动 echo 堆栈,或通过中间件注入调试 HTML —— 这种行为不受 display_errors 控制:
- Laravel 用户请确认
.env中APP_DEBUG=false,并检查app/Exceptions/Handler.php的render()方法是否跳过了开发态逻辑 - Symfony 用户检查
DEBUG=0环境变量,以及kernel.debug配置项是否为false - 任何自定义
set_exception_handler或register_shutdown_function都需审查,生产环境必须移除或用if (getenv('APP_ENV') !== 'dev')包裹
最容易被忽略的是框架层兜底输出——很多人改完 php.ini 和关掉 --debug,还是看到堆栈,就是因为框架自己又打印了一遍。上线前务必用真实错误触发一次,看响应体是否彻底“干净”。



















