根本原因是PHP进程全程读取并吐出二进制流,耗内存且受超时限制,Nginx未启用X-Accel-Redirect接管传输;需配置internal location、校验路径白名单、构造X-Accel-Redirect头并禁用response()->download(),同时按RFC 5987处理中文文件名。

ThinkPHP 6.0 中调用 response()->download() 下载大文件时频繁中断、卡死、内存溢出,浏览器提示“网络错误”或下载进度停在 99%,根本原因是 PHP 进程全程读取并吐出二进制流,既耗内存又受超时限制,而 Nginx 默认未启用加速机制来接管实际传输。
确认 Nginx 已启用 X-Accel-Redirect 支持
打开 Nginx 配置文件(如 /etc/nginx/nginx.conf 或站点 conf),检查是否已加载 ngx_http_x_accel_redirect_module 模块——该模块自 Nginx 0.7.10 起内置,无需额外编译,但必须在 location 块中显式启用 internal 指令。
在 server 或 http 块内添加如下配置段:
location /protected/ {
internal;
alias /var/www/storage/app/private/;
}
立即学习“PHP免费学习笔记(深入)”;
【/protected/ 必须以斜杠结尾,且 alias 路径末尾也必须带斜杠】 否则 Nginx 会拼接错误路径导致 404;修改后执行 nginx -t && nginx -s reload 生效。
PHP 层权限校验 + 构造 X-Accel-Redirect 头
第一步:获取原始文件路径,校验其是否落在白名单目录内
$realPath = realpath($userRequestedFile);
if (false === $realPath || 0 !== strpos($realPath, '/var/www/storage/app/private/')) {
throw new HttpException(403, '非法文件访问');
}
第二步:提取相对路径,用于构造 internal URI
$relPath = str_replace('/var/www/storage/app/private/', '', $realPath);
$internalUri = '/protected/' . ltrim($relPath, '/');
第三步:设置响应头并终止脚本输出
return response()->withStatus(200)
->header('Content-Type', 'application/octet-stream')
->header('Content-Disposition', 'attachment; filename="' . basename($realPath) . '"')
->header('X-Accel-Redirect', $internalUri)
->send();
注意:此处不能用 return response()->download(),它会自动写入 Content-Length 和 Cache-Control,与 X-Accel-Redirect 冲突;【X-Accel-Redirect 头必须是最终响应的唯一主体,前面不能有任何 echo/print 输出】
处理中文文件名乱码
方法一:使用 RFC 5987 格式重写 Content-Disposition
$filename = '报表_2024年Q2.xlsx';
$encoded = rawurlencode($filename);
->header('Content-Disposition', 'attachment; filename="' . $filename . '"; filename*=UTF-8\'\'' . $encoded)
方法二:若仅需兼容 Chrome/Firefox,可简化为
->header('Content-Disposition', 'attachment; filename="' . $filename . '"')
→ 此时 Nginx 不解析 filename*,但现代浏览器仍能正确解码 UTF-8 文件名
方法三:服务端强制转 ASCII(不推荐)
basename() 提取文件名后用 iconv('UTF-8', 'ASCII//TRANSLIT', $name) 替换中文为近似字符,适用于老旧 IE 环境
限速与缓冲控制(可选增强)
在返回响应前加入:
->header('X-Accel-Limit-Rate', '512k')
→ 限制客户端下载速度为 512KB/s,防止突发流量打满带宽
->header('X-Accel-Buffering', 'no')
→ 关闭 Nginx 缓冲,实现真正实时流式传输,适合监控类大文件
->header('X-Accel-Charset', 'utf-8')
→ 显式声明字符集,避免部分 Nginx 版本对 header 解析异常



















