header()必须在任何输出前调用,否则因HTTP头已被SAPI提交而报“headers already sent”致命错误;根本原因包括BOM、闭合标签后空白、include文件末尾空行等隐式输出,且Content-Type格式须严格为“application/json; charset=utf-8”,重定向需显式指定3xx状态码并加exit终止脚本。

header() 必须在任何输出前调用,否则直接报错且后续头全部失效 —— 这不是警告,是致命错误,脚本会中断或行为不可控。
为什么 header() 一调就报 “headers already sent”?
根本原因是 PHP 已经向 SAPI(如 Apache、PHP-FPM)提交了响应起始行和默认头,此时再发 header() 就违反 HTTP 协议规则。
- 常见静默输出源:
BOM 字节(UTF-8 with BOM 文件开头的 invisible 字节)、?>后多出的换行或空格、被include的配置文件末尾有空白行 - 哪怕只是
echo ''或var_dump([])前漏掉一个ob_start(),也会触发该错误 - 错误信息里带的文件路径和行号往往指向“输出发生处”,而不是
header()调用行 —— 要逆向排查所有前置文件
Content-Type 和 charset 怎么写才真正生效?
浏览器对字符集的解析高度依赖 Content-Type 头的精确格式,写错等于没写。
- 必须写成
Content-Type: application/json; charset=utf-8,不能拆成两行:header('Content-Type: application/json')+header('charset=utf-8')(后者会被忽略) -
charset必须全小写,等号前后不能有空格,utf-8中的横线不能省略(utf8在部分环境不识别) - 对 HTML 页面,如果同时用了
,服务端header()优先级更高;但若服务端没设,才 fallback 生效
重定向 Location 头为什么跳不过去?
表面调用了 header('Location: /login'),但浏览器没跳转,甚至返回了 200 或 404 —— 很可能状态码没显式指定。
-
header('Location: /login')默认沿用当前响应码(常为 200),而浏览器只对 3xx 状态码执行跳转 - 若之前已调用
http_response_code(404),再发Location也不会覆盖状态码,跳转失败 - 正确写法:
header('Location: /login', true, 302)(true表示替换已有同名头,302强制指定状态) - 重定向后必须接
exit或die,否则后续代码仍会执行,可能造成重复写库、泄露敏感信息
想改已发出的响应头?只能靠 ob_start(),但别当救命稻草
输出缓冲能让你在逻辑上“推迟”响应发送时间,但它不解决根本问题,反而掩盖隐患。
-
ob_start()必须放在脚本最开头(甚至比session_start()还早),否则缓冲未启用前的输出已不可逆 - 启用后,
header()可多次调用,同一头可被后一次覆盖(比如先设text/html,再改成application/json) - 生产环境依赖它来绕过输出检查,等于把编码/包含问题留到上线后爆发 —— 检查 BOM、清理空格、规范 include 顺序才是正解
最容易被忽略的是:错误发生在被包含文件里,而你只盯着主脚本。每次加新配置、引入新类库时,都要重新验证所有前置文件是否“零输出”。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











