PHP Warning: Cannot modify header information - headers already sent の修正方法

beginner🐘 PHP2026-03-22| PHP 7.x / 8.x(Linux: Apache・Nginx + PHP-FPM、Windows: XAMPP・WAMP)

Error Message

Warning: Cannot modify header information - headers already sent
#php#ヘッダー#出力#リダイレクト

状況

深夜2時。リダイレクトが機能していない。ユーザーはダッシュボードの代わりに空白のページが表示されている。ログにはこう出ている:

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

このメッセージがほぼすべてを物語っている。PHPがheader()setcookie()、またはsession_start()を呼び出そうとしたが、すでに何かがブラウザにバイトを送信していた。一度出力が送信されると、HTTPヘッダーはロックされる。それで終わりだ。

原因となるもの

原因は常に同じだ:ヘッダー呼び出しより前に何かが出力を生成している。厄介なのは、「出力」がほぼ目に見えない場合があることだ。

よくある原因:

  • 開始タグ<?phpの前にあるスペースや改行
  • ファイル先頭のUTF-8 BOM(ほとんどのエディタでは不可視 — 3つのサイレントバイト:EF BB BF
  • header()より前に呼ばれたechoprint
  • リダイレクト前にPHPタグの外で出力されたHTML
  • 出力が始まった後に呼ばれたsession_start()
  • 暗黙的に何かを出力するincluderequire

デバッグ手順

ステップ1:エラーメッセージをよく読む

PHPは出力がどこで始まったかを正確に教えてくれる。この部分に注目しよう:

(output started at /var/www/html/includes/config.php:1)

そのファイルのその行に移動しよう。そこが現場だ。

ステップ2:<?phpの前の空白を確認する

ファイルをhexエディタで開くか、次を実行する:

cat -A /var/www/html/includes/config.php | head -5

<?phpタグより前の行に^M$が見えるか?タグの前に何か文字がある?それが問題だ。不要なスペース1つですべてが壊れる。

ステップ3:UTF-8 BOMを確認する

次を実行する:

file /var/www/html/includes/config.php

出力にUTF-8 Unicode (with BOM)と表示されていれば、それが原因だ。その3つの不可視バイトが、PHPが開始する前に出力として送信される。

次のコマンドで削除する:

sed -i '1s/^\xEF\xBB\xBF//' /var/www/html/includes/config.php

またはVS Codeで開き、右下隅でエンコードをUTF-8 with BOMから通常のUTF-8に切り替える。

ステップ4:コード内の早まった出力を探す

ヘッダー呼び出しより前に現れるechoprint、またはHTMLを検索する:

grep -n 'echo\|print\|?>' /var/www/html/login.php

すべてを壊す典型的なパターンはこれだ:

<!-- bad: HTML before redirect -->
<html>
<head><title>Login</title></head>
<?php
session_start(); // 遅すぎる — すでにHTMLが送信済み
header('Location: /dashboard.php'); // これは失敗する
?>

解決策

修正1:すべてのヘッダーを出力より前に移動する

シンプルなルール:header()setcookie()session_start()は、空白行1つを含め、出力を生成するものより前に記述しなければならない。

<?php
// 正しい: sessionとheaderを最初に、この前には何もない
session_start();

if (!isset($_SESSION['user'])) {
    header('Location: /login.php');
    exit(); // リダイレクト後は常にexitする
}
?>
<!DOCTYPE html>
<html>
...

修正2:出力バッファリングを使用する

深夜2時にファイルを再構成するのはリスクが高い。出力バッファリングが時間を稼いでくれる:

<?php
ob_start(); // すべての出力をメモリに保持する

// echo/printはヘッダーを即座に送信しなくなる
echo 'something';

// これが機能するようになる
header('Location: /dashboard.php');
ob_end_flush();
exit();
?>

ob_start()はブラウザにフラッシュする代わりに出力をメモリに保持する。バッファがフラッシュされるまでヘッダーは変更可能なままだ。恒久的な修正ではないが、しっかりした応急処置になる。

php.iniで出力バッファリングをグローバルに有効にすることもできる:

output_buffering = 4096

その後、PHPプロセスを再起動する:

sudo systemctl restart php8.2-fpm
# または
sudo systemctl restart apache2

修正3:PHPの終了タグを削除する

閉じタグ?>の後の末尾改行は出力としてカウントされる。修正方法:それを削除する。閉じタグはPHPでは任意であり、純粋なPHPファイルでは省略することが推奨されている:

<?php
// 純粋なPHPファイル — 閉じ ?> は不要
define('DB_HOST', 'localhost');
define('DB_NAME', 'myapp');
// ファイル終端 — ?> なし

修正4:インクルードファイルに潜むBOMを探す

BOMはメインファイルにない場合もある。すべてのインクルードファイルを確認しよう:

find /var/www/html -name '*.php' | xargs grep -l $'\xef\xbb\xbf'

すべてのPHPファイルからBOMを一括削除する:

find /var/www/html -name '*.php' -exec sed -i '1s/^\xEF\xBB\xBF//' {} \;

修正の確認

壊れていたフローを直接テストする。リダイレクトの場合、curlでレスポンスヘッダーを確認する:

curl -I http://yoursite.com/login.php

正常に動作しているリダイレクトはこのように見える:

HTTP/1.1 302 Found
Location: /dashboard.php

まだLocationヘッダーなしの200 OKが表示されているか?どこかでまだ出力が漏れている。

すべての警告を表示するために、一時的に完全なエラーレポートを有効にする:

<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);

PHPが、不正な出力が発生している正確なファイルと行を教えてくれる。それを見つけて、修正して、完了だ。

得られた教訓

  • session_start()header()は絶対的な先頭に配置する — HTMLより前、echoより前、空白より前に。
  • PHPのみのファイルから閉じタグ?>を削除する。これは発火を待つ空白の罠だ。
  • エディタをBOMなしUTF-8で保存するよう設定する。UTF-8 BOMは不可視でサイレントであり、1時間のデバッグを消費させる。
  • 開発中の安全網として、アプリのエントリーポイントでob_start()を使用する — 恒久的なアーキテクチャ上の決定としてではなく。
  • エラーでインクルードファイルが指摘された場合、そのファイル内でPHPタグの外にある空白行1つでもこのエラーを引き起こすのに十分だということを忘れないように。

Related Error Notes