PHP 8.2 下载大文件被缓存的本质是HTTP链路复用旧响应,需禁用浏览器/代理缓存、关闭OPcache干扰、清空输出缓冲,并确保header()在无输出前提前单次完整设置,配合readfile()与Content-Length精确控制流式传输。

PHP 8.2 下载大文件时被缓存,本质是浏览器或中间代理复用了旧响应,而非 PHP 自身“缓存了文件内容”。关键不是“清空 PHP 缓存”,而是让整个 HTTP 链路明确拒绝复用——包括禁止浏览器缓存、绕过 OPcache 干扰、避免输出缓冲截断流。
header() 必须在任何输出前调用且不能重复设置 Cache-Control
PHP 8.2 对输出控制更严格,一旦有空白、BOM 或 echo/print 在 header() 前执行,就会报 Warning: Cannot modify header information,导致缓存头失效。而重复设置 Cache-Control(比如两次 header('Cache-Control: ...'))会让后一次完全覆盖前一次,可能只留下部分策略。
- 确保脚本开头无空格、无 UTF-8 BOM(用编辑器另存为“UTF-8 无 BOM”格式)
- 禁用所有可能触发输出的扩展(如 xdebug 的
xdebug.output_buffering) - 用单次完整声明替代多行拼凑:
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0'); - 不要加
false参数(如header('...', false)),PHP 8.2 中该用法已废弃且易出错
必须关闭 output buffering 才能流式传输大文件
PHP 默认启用输出缓冲(output_buffering = 4096 或 On),会导致 readfile() 或分块 fread() 的数据先存进内存缓冲区,等脚本结束才发给浏览器——这不仅增加内存压力,还可能触发超时或被 CDN/反向代理截断为“完整响应”后缓存。
- 在发送 headers 后、读取文件前,强制关闭并清空缓冲:
if (ob_get_level()) ob_end_clean(); - 不要用
ob_start()包裹下载逻辑——它和流式传输目标相悖 - 验证是否生效:下载时用 curl -I 看响应头是否有
X-Powered-By或X-Debug,有则说明缓冲未清干净
PHP 8.2 中 readfile() 比 fopen/fread 更安全但需配合 filesize()
readfile() 是 C 层直接 sendfile() 的封装,不经过 PHP 用户态内存,适合大文件;但它依赖 Content-Length 头准确,否则浏览器无法显示进度条,某些客户端(如 iOS Safari)会拒绝下载或卡住。
立即学习“PHP免费学习笔记(深入)”;
- 务必调用
filesize($path)并确认返回值非false(路径不存在、权限不足、符号链接断裂都会导致失败) - 对大于 2GB 的文件,
filesize()在 32 位系统或旧内核上可能溢出,此时改用stat($path)['size'](PHP 8.2 已修复该问题,但仍建议检查) - 避免在
readfile()前做耗时操作(如数据库查询、日志写入),防止连接超时;如有必要,用set_time_limit(0)临时解除限制
最容易被忽略的是:即使 PHP 层 headers 和流都正确,Nginx 的 fastcgi_buffering on 或 Cloudflare 的“Always Online”仍会把整个响应缓存下来。这类问题在 PHP 8.2 中不会报错,但下载行为会变得不可预测——比如首次正常、第二次 304、第三次直接 502。排查时优先 curl -v 看原始响应头,而不是只信浏览器开发者工具。



















