强制浏览器下载文件需严格设置三个 header:关闭缓存、Content-Type 为 application/octet-stream、Content-Disposition 指定 rawurlencode 编码的附件名,且须在任何输出前调用并避免 bom、空格、session 启动等干扰。

php 中用 header 强制浏览器下载文件的关键设置
直接生效的核心是三组 header:关闭缓存、声明内容类型为 application/octet-stream、用 Content-Disposition 指定附件名。漏掉任意一个,都可能导致文件被打开而非下载,或在某些浏览器(如 chrome 119+)中静默失败。
必须设置的三个 header 及其顺序和参数细节
顺序不能颠倒,且需在任何输出(包括空格、BOM、echo)之前调用:
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0'); header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename="' . rawurlencode($filename) . '"');
其中:
-
Cache-Control避免代理或浏览器缓存旧响应,no-store比no-cache更严格,推荐同时使用 -
Content-Type必须是无关联类型的二进制流,application/octet-stream最稳妥;不要用text/plain或推测 MIME 类型,否则 safari 可能直接渲染 -
filename值必须用rawurlencode()编码(不是urlencode()),否则中文名在 firefox 和 edge 中会乱码;双引号不可省略
下载前必须检查的常见错误点
这些错误不会报 PHP 错误,但会导致 header 失效:
- 脚本开头存在 UTF-8 BOM(尤其 windows 编辑器保存时默认添加),用
hexdump -C your.php | head检查前几字节是否为ef bb bf - 在
header()前有echo、var_dump()、甚至换行或空格 - 启用了
output_buffering但未显式ob_end_clean(),导致缓冲区已有内容 - 使用了
session_start()且 session 文件已写入,此时 PHP 自动发送Set-cookie,破坏 header 流程
完整可运行的最小示例(含安全处理)
以下代码假设文件路径已校验,不接受用户直传路径:
$file_path = '/var/www/files/report.pdf'; $filename = '2024年度报告.pdf'; if (!is_file($file_path) || !is_readable($file_path)) { http_response_code(404); exit; } ob_end_clean(); header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0'); header('Content-Type: application/octet-stream'); header('Content-Length: ' . filesize($file_path)); header('Content-Disposition: attachment; filename="' . rawurlencode($filename) . '"'); readfile($file_path); exit;
注意:Content-Length 虽非强制,但加上后能避免 Chrome 下载进度条卡在 99%,也方便断点续传支持。