header() 发送自定义响应头前必须确保无任何输出,因PHP会将空格、换行、echo、HTML或BOM字节视为已发送响应头,导致致命错误;需检查文件编码(UTF-8无BOM)、调用前无输出语句,并排查被包含文件的静默输出。

header() 发送自定义响应头前必须确保没输出
PHP 的 header() 函数本质是向 SAPI(如 Apache、FPM)写入响应头,一旦有任意输出(空格、换行、echo、HTML、BOM 字节),就会触发“headers already sent”错误。这不是警告,是致命错误,后续的 header() 全部失效。
- 检查 PHP 文件开头有没有 UTF-8 BOM —— 用编辑器(如 VS Code)设为“UTF-8 无 BOM”保存
- 确认
header()调用前没有echo、print、var_dump(),甚至文件末尾多一个换行也可能被某些配置捕获为输出 - 如果用了
require/include,要逐个检查被包含文件是否“静默输出”,比如 config.php 里写了?><?php中间夹了空格 - 启用输出缓冲(
ob_start())能临时绕过,但属于掩盖问题,不推荐在生产环境依赖它来修复 header 失败
Content-Type 和 charset 必须一起设,且顺序敏感
单独调用 header('Content-Type: application/json') 不会自动加 charset=utf-8;而浏览器对 JSON 响应若没明确 charset,可能按 ISO-8859-1 解析中文,导致乱码。反过来,如果先发了 header('Content-Type: text/html') 再补 header('charset=utf-8'),后者会被忽略 —— HTTP 头不允许重复字段覆盖,后发的同名头无效。
- 正确写法:
header('Content-Type: application/json; charset=utf-8') - 错误写法:
header('Content-Type: application/json'); header('charset=utf-8');(后者无效) - 对 HTML 页面,
Content-Type和<meta charset>应保持一致;但服务端header()优先级更高,<meta>只是 fallback
header() 第三个参数控制状态码,别漏掉或写错
很多开发者只传两个参数,以为 header('Location: /login') 自动带 302,其实它默认用当前响应的状态码(通常是 200)。如果上层已执行过 http_response_code(404),再发 Location 头,浏览器仍会按 404 处理重定向 —— 这会导致跳转失败或被拦截。
- 显式指定状态码更安全:
header('Location: /login', true, 302) -
true表示替换已有同名头(避免重复),false(默认)是追加,但多数头不允许重复,所以一般都用true - 常见组合:
header('HTTP/1.1 401 Unauthorized', true, 401)、header('Refresh: 3; url=/home', true, 200)
跨域头(CORS)不能只写 Access-Control-Allow-Origin
只加 header('Access-Control-Allow-Origin: *') 对简单请求够用,但一旦前端带了自定义头(如 X-Auth-Token)或用了 PUT/DELETE 方法,浏览器会先发预检(OPTIONS)请求。此时若后端没响应对应 CORS 头,预检失败,主请求根本不会发出。
立即学习“PHP免费学习笔记(深入)”;
- 必须配合:
header('Access-Control-Allow-Methods: GET, POST, OPTIONS') - 若需带 Cookie,
Access-Control-Allow-Origin不能为*,得写具体域名,且加上:header('Access-Control-Allow-Credentials: true') - 预检请求要手动处理:
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(200); exit; }
echo 了一行调试信息,header() 就彻底失灵。这种问题查起来费时间,但定位方法很固定:开 error_log,搜 “headers already sent”,然后顺藤摸到第一处输出位置。



















