PHP输出header的7个致命陷阱:90%的开发者都踩过这些坑
目录导读
- header()的基本规则 – 输出前不能有任何字符
- 常见错误:BOM头与空格 – 悄无声息的页面错乱
- 重定向(Location)的绝对路径误区
- 缓存控制(Cache-Control)与状态码的灵活搭配
- 文件下载场景的header组合技
- 多次header调用的覆盖与冲突
- 跨域(CORS)与安全头的现代实践
header()的基本规则:输出前不能有任何字符
header()函数用于发送原始HTTP头信息。最重要的铁律是:在调用header()之前,不能有任何输出(包括HTML、空格、BOM头),否则会报“Cannot modify header information - headers already sent”错误。

代码示例(错误示范):
<?php
echo "hello"; // 已经输出了
header("Location: https://example.com");
正确做法:确保PHP文件开头没有<?php之前的空格/换行/UTF-8 BOM,并且不要输出任何内容。
常见致命错误:BOM头与隐形空格
很多开发者忽略UTF-8 BOM头(就是文件开头的三个字节EF BB BF),当使用带BOM的编辑器保存PHP文件时,BOM会被直接输出到浏览器,导致header()失效。
解决方案:
- 使用无BOM的UTF-8编码(如VS Code选“UTF-8 without BOM”)。
- 检查包含其他文件时,被包含文件也可能带BOM。
重定向(Location)的绝对路径误区
Header("Location: ...") 在PHP中必须使用绝对URL,否则部分浏览器会忽略重定向。
// ❌ 错误
header("Location: /page.php");
// ✅ 正确
header("Location: https://example.com/page.php");
补充:如需过段时间跳转,可配合header("Refresh: 5; url=https://example.com"),但建议用JS或meta替代。
缓存控制(Cache-Control)与状态码的搭配
输出header时,状态码要与内容类型匹配,404页面需要http_response_code(404),否则浏览器可能缓存为200。
缓存控制组合:
header("Cache-Control: no-cache, no-store, must-revalidate"); // 防止缓存
header("Pragma: no-cache"); // HTTP/1.0兼容
header("Expires: 0");
若输出JSON,必须同时设置:
header("Content-Type: application/json; charset=utf-8");
文件下载场景的header组合技
当处理文件下载时,需要至少三个header:
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"".basename($file)."\"");
header("Content-Length: ".filesize($file));
readfile($file);
这里注意:filename需要URL编码或加引号,否则中文名会乱码,推荐用rawurlencode()。
多次header调用的覆盖与冲突
同一个HTTP响应中,多次调用header对同一个键,会以最后一次为准(除非使用“设置多个值”的语法,如Set-Cookie)。
header("X-Custom: first");
header("X-Custom: second");
// 最终结果是X-Custom: second
setcookie()函数注意:它会自动发送Set-Cookie头,多次调用没问题,但参数名冲突时,建议使用数组setcookie("key[]", "val")。
跨域(CORS)与安全头的现代实践
现在前后端分离场景下,Access-Control-Allow-Origin必须按需设置:
header("Access-Control-Allow-Origin: https://trusted-site.com");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, Authorization");
安全头推荐设置(防御XSS/点击劫持/嗅探):
header("X-Frame-Options: SAMEORIGIN");
header("X-Content-Type-Options: nosniff");
header("Referrer-Policy: strict-origin-when-cross-origin");
header("Content-Security-Policy: default-src 'self'");
问答专区(核心FAQ)
问1:为什么我写了header("Location:")却跳转失败?
答:检查三处:①前面是否有任何echo/HTML输出/BOM(用die()或exit()在header后确保立即终止);②URL是否为绝对路径;③是否ob_start()开启缓冲区——如果开着,可以先ob_end_clean()清空缓冲区。
问2:header("Content-Type: text/html; charset=utf-8")后,页面中文还是乱码?
答:这可能是因为PHP文件本身保存的编码不是UTF-8(比如是GBK),并且数据库连接也需设置UTF-8,用mb_internal_encoding("UTF-8")和mysql_set_charset('utf8')确保全链路统一。
问3:动态生成图片/PDF时,header怎么配合?
答:图片用header("Content-Type: image/png"),PDF用header("Content-Type: application/pdf"),同时加header("Content-Disposition: inline; filename=\"...\"")表示浏览器内显示,attachment则强制下载。
问4:能否在HTML页面中间写header?
答:绝对不可以,如果需要,必须用ob_start()开启输出缓冲,然后在任意位置调用header,最后ob_end_flush()输出,但更推荐逻辑控制——在页面最顶部处理所有header。
问5:如何调试header是否发送成功?
答:用PHP的内置函数headers_list()查看已设置的header,或者在Chrome开发者工具的Network标签下查看响应头,可开启error_reporting(E_ALL),并在php.ini设置display_errors=On以捕获警告。
输出header虽是个小函数,但关系到SEO抓取、页面性能、安全防护。核心三原则:无输出、得用绝对URL、配上状态码,建议开发时将PHP文件的编码、空白字符管理纳入代码规范,如需排查,优先使用headers_sent()函数检测问题位置:
if (!headers_sent()) {
header("X-Example: test");
}
掌握这7个注意点,你的PHP HTTP交互将更加稳健,也能在搜索引擎的爬虫审计中拿到更高好感分。