https 移行後に出る WordPress の混在コンテンツを解消する
https へ移行した後の WordPress 混在コンテンツ警告を解消する方法。データベースに残った http:// の URL を洗い出し、安全に置換し、.htaccess で https を強制します。
公開
証明書は導入済み、サイトは https で表示される、それでも鍵アイコンが一向に出ない。それが混在コンテンツです。ページ自体は https で届いているのに、その中にある何か(画像、スタイルシート、スクリプト)が今も平文の http:// で要求されています。この記事では、そうした URL を見つけ、シリアライズされたデータを壊さずに置換し、siteurl と home を直し、サーバー側で https を強制して二度と再発しないようにするまでを追います。
混在コンテンツとは実際に何か
証明書の導入で変わるのは通信の暗号化方法です。ページが何を要求するかは変わりません。移行前に投稿、ウィジェット、テーマ設定へ書き込まれた http://yoursite.com/wp-content/uploads/logo.png は、今もそのままデータベースに座っており、ブラウザーは律儀にそれを安全でない接続で取りに行きます。
ブラウザーは二つのカテゴリーを別々に扱います。この区別は緊急度を判断するときに効いてきます。
- 能動的な混在コンテンツ — スクリプト、スタイルシート、iframe、XHR。完全にブロックされます。https 移行後にエラーメッセージ一つ出ないままサイトが壊れて見えるのは、これが理由です。スタイルシートが黙って拒否されたのです。
- 受動的な混在コンテンツ — 画像、音声、動画。通常は読み込まれますが、鍵アイコンの評価が下がり、一部のブラウザーは「保護されていない通信」と表示します。
どちらも直す価値があります。ただし壊れるのは前者だけです。
手順 1:まだ安全でないものを洗い出す
まずブラウザーから始めます。ページを開き、開発者ツールを開き、コンソールを読んでください。ブロックされたリクエストも評価が下がったリクエストも、完全な URL 付きで名指しされています。そこで分かるのは何が安全でないかだけで、それがどこに保存されているかは分かりません。
そちらはデータベースを直接見ます。エクスポートしてダンプを検索してください。
wp db export dump.sql
grep -o "http://example\.com[^\"']*" dump.sql | sort -u | head -50
WP-CLI が使えない環境では、mysqldump でも同じファイルが作れます。
mysqldump -u USER -p DBNAME > dump.sql
返ってきた一意な URL を読みます。アップロードのパスは投稿本文とメタを指しています。テーマのアセットのパスはたいていオプションを指します。見覚えのないドメインは外部の埋め込みで、対処法が異なります(後述)。
手順 2:まず siteurl と home を直す
siteurl と home は、WordPress が生成するほぼすべての内部 URL の組み立てに使う二つのオプションです。どちらかが http:// のままだと、データベースの他がどれだけきれいでも、WordPress は安全でない URL を出し続けます。
確認します。
wp option get siteurl
wp option get home
sql ならこうです。
SELECT option_name, option_value
FROM wp_options
WHERE option_name IN ('siteurl', 'home');
この二つはシリアライズされた配列ではなく素の文字列なので、ここでは sql による直接更新が本当に安全です。
UPDATE wp_options
SET option_value = REPLACE(option_value, 'http://example.com', 'https://example.com')
WHERE option_name IN ('siteurl', 'home');
作業の前に落とし穴が一つ。wp-config.php が WP_HOME や WP_SITEURL を定義している場合、これらの定数はデータベースを完全に上書きするため、更新しても何も起きていないように見えます。
define( 'WP_HOME', 'https://example.com' );
define( 'WP_SITEURL', 'https://example.com' );
そちらを書き換えるか、削除してデータベースに任せてください。ここを先に確認しましょう。「変えたのに何も起きない」という事例の多くはこれで説明が付きます。
手順 3:置換はシリアライズ対応でなければならない
データベースの残り全部が本当のリスクの在り処です。しかもそれは、多くの人が予想するリスクとは違います。危ないのは置換が URL を取りこぼすことではありません。テキストとしては置換に成功したうえで、周囲のデータを破壊することです。
WordPress は配列やオブジェクトを PHP のシリアライズ文字列として保存し、その形式は内部の各文字列のバイト長を記録します。
a:1:{s:4:"logo";s:38:"http://example.com/img/logo.png";}
生の sql の REPLACE で http:// を https:// に変えると、テキストは 1 バイト長くなるのに、宣言された長さは古い値のままです。PHP はその長さを読み、その分だけ前に進み、期待した位置に終端が見つからず、配列全体のアンシリアライズを拒否します。すると WordPress は、その設定が一度も保存されなかったかのように振る舞う値をテーマやプラグインに渡します。
症状はエラーではありません。ウィジェットが消え、カスタマイザーの設定が初期化され、ページビルダーのレイアウトが真っ白になる。しかも管理画面には何も異常が示されません。バックアップなしでそこから復旧するのは本当に骨が折れるので、必ず取っておいてください。
wp db export backup-before-https-replace.sql
そのうえで、アンシリアライズし、復元した構造の内側で置換し、長さを直してシリアライズし直すツールを使います。まずドライランです。
wp search-replace 'http://example.com' 'https://example.com' --dry-run --all-tables --skip-columns=guid
レポートを読んでください。見覚えのないテーブルで何千件もヒットしているなら、確定させる前に止めて確認しましょう。問題なければ本番実行します。
wp search-replace 'http://example.com' 'https://example.com' --all-tables --skip-columns=guid --report-changed-only
--skip-columns=guid は重要です。guid はフィードリーダー向けの恒久的な識別子であって生きた URL ではありません。書き換えると、購読者にはアーカイブ全体が新着記事として見えてしまう可能性があります。実行前に、どの列が素の sql で安全でどの列がシリアライズ対応の処理を要するかを確認したい場合は、WordPress 検索置換 sql ツールで文を組み立ててください。
コマンドラインが使えない場合は、シリアライズされたデータを扱えると明記している移行プラグインが、管理画面から同じデコード処理を行ってくれます。そう書かれていないツールは、対応していないものとみなしてください。
手順 4:サーバー側で https を強制する
データベースの掃除で止められるのは、ページが安全でないリソースを要求することです。訪問者が最初から http:// で到達するのを止めることはできません。それはサーバーレベルのリダイレクトであり、Apache では .htaccess の担当です。
# BEGIN Force HTTPS
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</IfModule>
# END Force HTTPS
置き場所について二点。これは # BEGIN WordPress のマーカーより上に置いてください。マーカーの内側は、誰かがパーマリンクを保存するたびに破棄されます。またこれが機能するのは mod_rewrite が有効で、バーチャルホストが上書きを許可している場合(AllowOverride All、少なくとも FileInfo)に限られます。nginx では .htaccess は完全に無視されるため、代わりに server ブロックへ書く必要があります。
RedirectMatch ではこの仕事はできません。一致判定はリクエストパスに対してのみ行われ、現在のリクエストがすでに安全かどうかを判定する手段がないため、https のリクエストを自分自身へリダイレクトしてしまいます。上の条件付き RewriteRule が正しい道具です。
リダイレクトループと、その原因
そのルールを追加した途端にサイトが延々とリダイレクトし始めたなら、TLS は上流のどこか、ロードバランサー、リバースプロキシ、あるいは CDN で終端されており、そこから Apache へ平文の http が転送されています。Apache は安全でないリクエストを見て https へリダイレクトし、プロキシがまた http を転送し、同じことが繰り返されます。
代わりに転送用のヘッダーを判定してください。
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
WordPress 自身も同じ構成では同じ盲点を抱えています。is_ssl() は $_SERVER['HTTPS'] を読みますが、プロキシはこれを設定しないため、管理画面の URL が http:// で出力されます。wp-config.php の「編集をやめる」行より上に、これを追加してください。
if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && 'https' === $_SERVER['HTTP_X_FORWARDED_PROTO'] ) {
$_SERVER['HTTPS'] = 'on';
}
リスクについては率直に言っておきます。X-Forwarded-Proto はクライアントが送るヘッダーです。これを信頼してよいのは、自分が管理するプロキシが必ず上書きしている場合だけです。インターネットから直接到達できるサーバーでは偽装できます。
データベースの置換では直らないもの
- ファイルに直書きされた URL。
functions.php、子テーマのテンプレート、インラインのアセットパスに書かれたものはデータベースにありません。テーマのディレクトリは別途 grep してください。 - エスケープされた JSON。 一部のビルダーは URL を
https:\/\/example.comの形で保存します。通常形での検索では完全に取りこぼすため、エスケープされた文字列に対する二度目の置換が必要になることがあります。 - 外部リソース。 http でしか提供されていない第三者のスクリプトや埋め込みは、こちら側では直せません。https 版を探すか、外してください。
- キャッシュ。 ページキャッシュ、オブジェクトキャッシュ、CDN の複製は、正しく置換した後も古いマークアップを配信し続けます。置換が失敗したと結論づける前に、三つとも消去してください。
一つ、見送ってよいものがあります。upgrade-insecure-requests の Content Security Policy ヘッダーは、ブラウザー側で安全でないリクエストを書き換えて警告を黙らせます。しかしそれは症状に手当てをするだけで、誤った URL はデータベースに残ったままです。次のエクスポート、移行、フィードがそれを持ち越します。まずデータを直し、そのうえで安全網として使いたければ使ってください。
それでも解決しない場合
各手順のあとに siteurl と home を再確認してください。プラグインや移行ツールが裏で書き換えることがあります。コンソールが名指しする安全でない URL がダンプの中に見つからない場合は、ページのソースを表示してそこから検索してください。生成されたマークアップには現れるのにデータベースにはないなら、それは PHP 側で組み立てられています。それを出力しているテーマかプラグインこそが直すべき対象です。
FAQ
よくある質問
SSL 証明書を入れたのに WordPress サイトで混在コンテンツの警告が消えないのはなぜですか?
証明書が変えるのは通信の暗号化方法だけで、ページが何を読み込もうとするかは変わりません。古い http:// の URL は、投稿本文・ウィジェットの設定・テーマの設定としてデータベースに残ったままです。ブラウザーはページ自体を https で読み込んだうえで、画像やスクリプトが平文の http で要求されているのを検知し、本来は安全なページで混在コンテンツとして報告します。
WordPress のデータベースに残っている http:// の URL はどうやって探しますか?
まずブラウザーでページを開き、開発者コンソールを読みます。安全でないリクエストがすべて URL 付きで列挙されます。次にデータベースをエクスポートし、ダンプ内を http で始まる自ドメインで検索してください。テーブルごとにヒット数を数えれば、問題が投稿本文にあるのか、wp_options にあるのか、想定外のプラグインのテーブルにあるのかが分かります。
単純な sql の検索置換で混在コンテンツを直せますか?
siteurl や home のようなスカラー値だけです。WordPress がシリアライズされた配列として保存するもの、つまり大半のオプション値やメタ値には、内部の各文字列のバイト長が記録されています。生の置換ではテキストだけが変わって長さが古いままになり、値のアンシリアライズに失敗し、その設定は何も告げずに初期値へ戻ります。
WordPress で https を強制する .htaccess のルールは?
https の変数を判定する条件を付けた RewriteRule を、BEGIN WordPress のマーカーより上に置きます。そうすればパーマリンクの保存で消えません。プロキシや CDN の背後では、安全なリクエストでも https の変数が off と読めるため、代わりに X-Forwarded-Proto ヘッダーを判定してください。さもないとルールは延々とリダイレクトし続けます。
https を強制したらリダイレクトループに陥りました。なぜですか?
ほぼ確実に、TLS がプロキシや CDN で終端され、そこから Apache へ平文の http が転送されています。リライトは安全でないリクエストを見て https へリダイレクトし、プロキシがまた http を転送し、これが繰り返されます。条件を X-Forwarded-Proto ヘッダーに切り替え、wp-config.php で https のサーバー変数を設定してください。
混在コンテンツはページ全体を壊すのですか、それとも鍵アイコンだけの問題ですか?
リソースの種類によります。スクリプト・スタイルシート・iframe といった能動的な混在コンテンツは、ブラウザーが完全にブロックします。目に見える説明もないままレイアウトや機能が壊れることがあります。画像や動画などの受動的な混在コンテンツは通常は読み込まれますが、鍵アイコンの評価は下がり、訪問者には保護されていない旨の表示が出ることがあります。どちらも直す価値があります。