WordPress管理画面が真っ白に!原因はPowerShell 5.1が付けたBOMでした
WordPressにAdSenseコードを設置したあと、管理画面にログインしようとしたらページが真っ白になった。
原因調査したところ、PowerShell 5.1で作成したPHPファイルに含まれるBOMが原因。
同じ問題に遭遇した人の参考になれば。
何をしていたか
私のWordPressはOracleクラウドのVPS(Virtual Private Server、インターネット上に借りる自分専用のサーバー)上でDockerを使って運用してます。
今回ついにAdSense審査ということで審査用のコードをWordPressの全ページの<head>に設置するため、mu-plugins(Must-Use Plugins、WordPressが起動時に必ず自動で読み込む特殊なプラグインフォルダ。通常のプラグインと違い、管理画面から無効化できない)としてPHPファイルを作成してアップロードした。
作業の流れは以下。
- WindowsのPowerShell 5.1でPHPファイルを作成
- SCPでOracleクラウドのサーバーに転送
- DockerコマンドでWordPressコンテナ内にコピー
しかし、設置後すぐに管理画面(wp-login.php)を開いたところ、ページが真っ白になっていた。
エラーの確認
サーバーは正常に動いてる。Dockerコンテナも全て起動中。外部からHTTPステータス(Webサーバーがリクエストに対して返す結果コード。200=正常、404=ページが見つからない、503=サーバーダウンなど)を確認すると200が返ってくる。
つまりサーバー自体は生きていてページを返せているが、中身のHTMLを見ると正常なログインフォームではなくエラー状態だった。「外から見ると正常そうに見えるのに実際は壊れている」という状況。
WordPressコンテナ内から直接ログインページのHTMLを取得したところ、エラーメッセージが見つかった。
エラー: 予期しない出力により Cookie がブロックされました。
「予期しない出力」がCookieの設定を妨げているということだ。WordPressのログインはCookie(ブラウザがログイン状態を記憶するために保存する小さなデータ)を使って認証するため、Cookieがセットできない=ログイン不可=白画面、という状態になっていた。
原因:PHPファイルの先頭にBOMが含まれていた
直前にアップロードしたPHPファイルが怪しいと判断した。
根拠は2つ。
①PHPファイルをアップロードした直後に問題が発生しており、それ以外に変更は何もしていない。
②エラーメッセージの「予期しない出力」はPHPファイルが何かを意図せず出力しているという意味で、mu-pluginsはログインページを含む全ページで読み込まれるため、新たに追加したファイルが出力元として最も疑わしかった。そこでファイルの先頭バイトを確認。
正常なPHPファイルの先頭は<?php(PHPプログラムの開始を示す宣言文)の最初の文字<を示す0x3Cから始まる。
ところが問題のファイルの先頭は0xEF 0xBB 0xBFの3バイトだった。
これがBOM(Byte Order Mark)。
BOMとは何か
BOMはファイルの文字コードを識別するための目印として先頭に付加される特殊なバイト列。UTF-8(日本語を含む世界中の文字を扱えるテキストの文字コード規格)の場合はEF BB BFの3バイト。
目には見えない。
PHPはファイルを読み込む際、<?phpタグより前にある文字をそのままHTMLとしてブラウザに出力する。
BOMも例外ではなく、見えない3バイトが出力される。
ここで、HTTPの通信を理解するために、以下3者を整理する。
- ブラウザ(自分のPC上):WordPressにアクセスしてページを要求する側
- PHP(サーバー上):サーバー側でプログラムを実行するエンジン。wp-login.phpなどのファイルを読み込んで処理し、結果をブラウザに返す
- WordPress(サーバー上):PHPによって実行されるブログ管理ソフト。ログイン処理やCookieの発行を担当する
HTTPの通信はヘッダーとボディという2段構造になっている。ヘッダーは「Cookieをセットしろ」といった指示情報、ボディは実際のHTMLコードだ。そしてHTTPの仕様上、ヘッダーは必ずボディより先に送らなければならない。
本来の正常な流れは以下。
①ブラウザ(自分のPC)→ サーバーに「ログインページをください」とリクエスト送信
↓
②サーバー上のPHPがwp-login.phpを読み込んでWordPressを起動
↓
③WordPress(サーバー上)がログイン処理を準備し「このCookieをブラウザにセットしろ」という指示をヘッダーに乗せてブラウザに送信
↓
④ブラウザ(自分のPC)がCookieを受け取って保存(このCookieはセキュリティトークンとして機能する。次のステップでIDとパスワードを送信する際にこのCookieも一緒に送ることで、WordPressが「正規のブラウザからのリクエストか」を照合できる)
↓
⑤PHP(サーバー上)がログインフォームのHTMLコード(ボディ)をブラウザに送信
↓
⑥ブラウザがHTMLを表示 → ログインフォームが見える
BOMがある場合はこれが狂う。
PHPはファイルを読み込む際、<?phpタグより前にある文字を即座にボディとしてブラウザに送ってしまう。
今回のadsense.phpはBOMが先頭にあるため、WordPressが起動する前の段階でPHPがBOMをブラウザに送信してしまう。
①ブラウザ → サーバーに「ログインページをください」とリクエスト送信
↓
②サーバー上のPHPがadsense.phpを読み込んだ瞬間、先頭のBOMをボディとしてブラウザに送信してしまう
↓
③WordPressが適切なヘッダー(Cookieのセット指示を含む)を送ろうとするが、すでにボディ送信済みのためヘッダーを送れない
↓
④ヘッダーとボディを正しい順序で送れなかったことで通信が破綻、なおかつすでに送られているボディの中身がBOMの3バイトだけで表示できるコンテンツがほぼ何もない
↓
⑤ログイン白画面
なぜPowerShell 5.1でBOMが付いたのか
WindowsはもともとShift-JISやANSIという文字コードを使ってきた。
UTF-8が普及した際、WindowsアプリがUTF-8ファイルを識別できるよう、Microsoftがファイル先頭にBOMを付ける慣習を作った。
PowerShell 5.1はそのWindows流儀を引き継いでいるため、Out-File -Encoding utf8(PowerShellでファイルを保存するコマンド。Out-Fileが「ファイルに書き出せ」、-Encoding utf8が「UTF-8という文字コードで保存しろ」という意味)では必ずBOM付きで保存される。
一方、Linux・PHP・Webの世界では「UTF-8にBOMは不要」が標準。
PowerShell 7以降はこの点を改め、BOMなしがデフォルトになってる。
| バージョン | Out-File -Encoding utf8の挙動 |
|---|---|
| PowerShell 5.1(Windows標準) | BOM付き ⚠️ |
| PowerShell 7以降 | BOMなし ✅ |
対策
もともとは以下のコードでPHPファイルを保存していた。
$phpContent | Out-File -FilePath "adsense.php" -Encoding utf8
Out-FileはPowerShellでファイルに書き出す命令、-Encoding utf8はUTF-8で保存する指定。一見問題なさそうだが、PowerShell 5.1の-Encoding utf8はBOM付きで保存するため、意図せずBOMが混入していた。
対策としてOut-Fileの代わりに[System.IO.File]::WriteAllText()を使う。これは.NET(Windowsに標準搭載されているプログラムの部品群)のファイル書き込み機能を直接呼び出すもので、文字コードの指定を細かくコントロールできる。
[System.IO.File]::WriteAllText(
"C:\path\to\file.php", # 第1引数:保存先のファイルパス
$content, # 第2引数:保存する内容
[System.Text.UTF8Encoding]::new($false) # 第3引数:UTF-8・BOMなしを指定($false=BOM不要)
)
第3引数の[System.Text.UTF8Encoding]::new($false)が肝で、$falseを渡すことで「BOMを付けるな」という明示的な指定になる。Out-Fileでは制御できなかった部分をここで解決している。
PowerShell 7をインストールすればOut-File -Encoding utf8でもBOMなしになるが、5.1のままでもこの方法で対処できる。
まとめ
- WordPress管理画面が白画面になったら「予期しない出力によるCookieブロック」を疑う
- 直前にPHPファイルを追加・編集していた場合、そのファイルのBOMが原因の可能性がある
- PowerShell 5.1で作成したPHPファイルは要注意
- 対策は
[System.IO.File]::WriteAllText()でBOMなしUTF-8を指定する