强制浏览器下载文件需严格设置三个 header:关闭缓存、Content-Type 为 application/octet-stream、Content-Disposition 指定 rawurlencode 编码的附件名,且须在任何输出前调用并避免 BOM、空格、session 启动等干扰。
直接生效的核心是三组 header:关闭缓存、声明内容类型为 application/octet-stream、用 Content-Disposition 指定附件名。漏掉任意一个,都可能导致文件被打开而非下载,或在某些浏览器(如 Chrome 119+)中静默失败。
顺序不能颠倒,且需在任何输出(包括空格、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 失效:
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 = '2025年度报告.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: ap
plication/octet-stream');
header('Content-Length: ' . filesize($file_path));
header('Content-Disposition: attachment; filename="' . rawurlencode($filename) . '"');
readfile($file_path);
exit;注意:Content-Length 虽非强制,但加上后能避免 Chrome 下载进度条卡在 99%,也方便断点续传支持。