◀ 11.

【PHPエラー】Warning: Cannot modify header information - headers

▶
この記事の要点
  • header() を呼ぶ前に、何かがブラウザへ出力されているのが原因
  • エラー文の output started at ファイル名:行番号 が「最初に出力した場所」。まずそこを見る
  • 犯人の定番は echo・HTML・<?php の前の空白・?> の後ろの改行・UTF-8 の BOM・Notice などのエラー表示
  • 根本対処は「出力より前に header() を呼ぶ」こと。PHP だけのファイルでは閉じタグ ?> を書かない
  • ob_start() で回避もできるが、原因を隠すだけなので最終手段

エラー内容

Warning: Cannot modify header information - headers already sent by (output started at /var/www/html/config.php:15) in /var/www/html/login.php on line 22

「ヘッダー情報を変更できません。ヘッダーは既に送信されています(出力は config.php の 15 行目で始まりました)」という意味です。in ... on line 22 は失敗した header() の場所、output started at は先に何かを出力してしまった場所です。直すべきなのは後者です。

原因: HTTP ヘッダーは本文より先に送られる

HTTP のレスポンスは「ヘッダー → 空行 → 本文」の順でブラウザに送られます。PHP は、スクリプトが最初に 1 バイトでも本文を出力した時点でヘッダーを確定して送信します。その後で header() を呼んでも、送信済みのヘッダーは変更できないため、この警告が出てリダイレクトや Cookie の設定が効きません。

同じ理由で、setcookie() や session_start()(セッション Cookie を送るため)も、出力の後に呼ぶと同種の警告になります。

よくある出力の犯人

犯人例
echo / print / var_dumpデバッグ用に書いた var_dump($user); が残っている
PHP タグの外の HTMLファイル冒頭に <!DOCTYPE html> があり、その後で header() を呼んでいる
<?php より前の空白・改行ファイル 1 行目が空行になっている
?> より後ろの空白・改行require した設定ファイルの末尾に改行が残っている
UTF-8 の BOMエディタが保存時にファイル先頭へ不可視の 3 バイトを付けた
エラーメッセージdisplay_errors が有効で、Notice や Deprecated が先に画面に出た

対処法

1. header() を出力より前に移動する

リダイレクトや Cookie の判定処理は、HTML を出力し始める前に済ませます。

<?php
// NG: 先に HTML を出してから header()
// echo '<p>ログイン中...</p>';
// header('Location: /mypage.php');

// OK: 判定とリダイレクトを先に行う
session_start();
if (!isset($_SESSION['user_id'])) {
    header('Location: /login.php');
    exit; // リダイレクト後は必ず処理を止める
}
?>
<!DOCTYPE html>
<html>
...

2. 閉じタグ ?> を書かない

関数定義や設定だけの PHP ファイルでは、末尾の ?> を省略します。PHP は閉じタグがなくても問題なく動き、閉じタグの後ろに紛れ込んだ改行が出力される事故を防げます。PSR-12 などのコーディング規約でも、PHP だけのファイルでは閉じタグを省略するよう定めています。

3. BOM を取り除く

エディタの保存形式を「UTF-8(BOM なし)」にして保存し直します。VS Code ならステータスバーの文字コード表示から「エンコード付きで保存」→「UTF-8」を選びます。Linux では次のコマンドで BOM 付きファイルを探せます。

# BOM (EF BB BF) で始まる .php ファイルを一覧表示
grep -rl $'^\xEF\xBB\xBF' --include='*.php' .

# 1 ファイルの先頭 3 バイトを確認(efbbbf なら BOM あり)
head -c 3 config.php | xxd -p

4. エラー表示を画面に出さない

本番環境では display_errors = Off にしてエラーはログに出します。ただし、Notice が出ていること自体が不具合のサインなので、その原因も直しておきます。

最終手段: 出力バッファリング

スクリプト冒頭で ob_start(); を呼ぶか、php.ini の output_buffering を有効にすると、出力がいったんバッファに溜まるため警告は出なくなります。しかし原因の出力(余計な空白やデバッグ表示)はそのまま残り、JSON や画像のダウンロードが壊れる原因にもなります。既存システムの応急処置にとどめ、上の 1〜3 で根本から直すのがおすすめです。

確認方法

どこで出力が始まったかをコードから調べるには headers_sent() が便利です。

<?php
if (headers_sent($file, $line)) {
    error_log("出力開始: {$file} の {$line} 行目");
} else {
    header('Location: /mypage.php');
    exit;
}

修正後はブラウザの開発者ツール(ネットワークタブ)でレスポンスを確認し、リダイレクトなら 302 などのステータスと Location ヘッダーが返っていれば解決です。

よくある質問

ローカルでは出ないのにサーバーでだけ出る

php.ini の output_buffering の設定が環境によって違うことが多い原因です。ローカルで出力バッファリングが有効だと、余計な出力があってもバッファに溜まって警告になりません。サーバーで無効になっていると、同じコードで警告が出ます。php -i | grep output_buffering や phpinfo() で両方の値を見比べてください。どちらの環境でも動くよう、コード側で出力の順序を直すのが確実です。

WordPress で突然出るようになった

テーマの functions.php やプラグインのファイルを編集した後に出る場合は、編集したファイルの末尾にある ?> の後ろの空行や、ファイル先頭の BOM が典型的な原因です。エラーメッセージの output started at に、そのファイル名が表示されているはずです。

関連

Post Share
子ページ

子ページはありません

同階層のページ
  1. Fatal error: Maximum execution time of 30 seconds exceeded in...
  2. Fatal error: Uncaught Error: Cannot use object of type stdClass as array in ...
  3. Warning: Use of undefined constant ... - assumed '...' (this will throw an Error)
  4. ERROR: Call to undefined method Maatwebsite\Excel\Excel::load()
  5. Maximum execution time of 30 seconds exceeded
  6. Your requirements could not be resolved to an installable set of packages. ... To enable extensions, verify that they are enabled in your .ini files:
  7. could not find driver
  8. the requested PHP extension mbstring is missing from your system.
  9. the requested PHP extension dom is missing from your system.
  10. A non well formed numeric value encountered
  11. Warning: Cannot modify header information - headers already sent by ...
  12. php_network_getaddresses: getaddrinfo failed: Name or service not known
  13. XMLWriter::openUri(): Unable to resolve file path
  14. Object of class stdClass could not be converted to string
  15. Class 'Google_Service_Youtube' not found