根本原因是PHP默认输出缓冲机制导致文件全量驻留内存;需关闭ob/zlib、改用fread+flush分块输出,或启用X-Sendfile由Web服务器接管传输。

FrankenPHP 下载大文件时内存飙升,根本原因不是 FrankenPHP 本身,而是 PHP 默认输出机制在缓冲环境下的行为——哪怕你用 readfile(),只要输出缓冲(ob)或压缩(zlib)开启,整个文件就会被暂存在内存里再一次性送出。FrankenPHP 虽基于 SAPI 重构,但依然遵循 PHP 的输出缓冲规则,所以老问题照旧发生。
确认并关闭所有输出缓冲层
FrankenPHP 默认可能启用 ob 或自动 gzip,必须显式清空:
- 在输出头之前加循环清空:
while (ob_get_level()) ob_end_clean(); - 禁用运行时压缩:
ini_set('zlib.output_compression', 0); - 避免框架自动开启 ob(如 Laravel 的中间件、Symfony 的 HttpKernel),检查是否调用了
ob_start()
改用 fread + flush 分块流式输出
这是 FrankenPHP 下最稳妥、零 Web 服务器配置依赖的方案:
- 打开文件句柄后逐块读取(建议 512KB–1MB),每次
echo后立即flush() - 确保响应头完整:
Content-Type、Content-Disposition、Content-Length(必须提前知道文件大小) - 示例关键段:
ignore_user_abort(true); // 防止用户中断影响进程
if (ob_get_level()) ob_end_clean();
ini_set('zlib.output_compression', 0);
$fp = fopen($file_path, 'rb');
if (!$fp) die('File not readable');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . basename($file_path) . '"');
header('Content-Length: ' . filesize($file_path));
header('X-Accel-Buffering: no'); // Nginx 兼容提示,FrankenPHP 会忽略但无害
while (!feof($fp)) {
echo fread($fp, 1048576);
if (connection_aborted()) break;
flush();
}
fclose($fp);
?>
启用 X-Sendfile(推荐用于生产)
FrankenPHP 支持 X-Sendfile(通过 Caddy 或 Nginx 中转),让 PHP 只做鉴权,不碰文件内容:
立即学习“PHP免费学习笔记(深入)”;
- FrankenPHP 不直接发送文件,只返回带
X-Sendfile头的响应 - Caddy 配置需启用
file_server并允许内部重写;Nginx 需开启sendfile on和underscores_in_headers on - 代码只需:
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . basename($file_path) . '"');
exit;
此时内存占用恒定在几 KB,彻底规避 PHP 层传输压力。



















