WordPressインストール画面が崩れると思ったらcssパスがhttp‥ConoHaで発生するというので調べたら ロードバランサー/Reverse-Proxyにまつわる問題だった、というお話

お客様がお持ちのConoHaレンタルサーバーにウェブサービスを構築する案件がありました。
一部でWordPressを使うのでインストールしようとしたところ、インストール画面で表示が崩れるという問題が起きました。
そんな報告を社員さんから受けたので、調べて問題を解決しました。
もし似たような問題で悩まれている方は参考にしてください。

ちなみにですが、画面は崩れてもインストール作業は続けられます。
が、インストールが終わり、ログイン画面でログインしようとしても、ログインは出来ません。
悪あがきや小手先の技では解決しません。

なぜWordPressインストール画面が崩れたのか?

画面崩れについては、インストール画面のHTMLソースをみればイッパツでわかります。

<!DOCTYPE html>	
	<html lang="ja" xml:lang="ja">
	<head>
	<meta name="viewport" content="width=device-width" />
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
	<meta name="robots" content="noindex,nofollow" />
	<title>WordPress &rsaquo; インストール</title>
	<link rel='stylesheet' id='dashicons-css' href='http://●●●●/wp-includes/css/dashicons.min.css?ver=6.2.2' type='text/css' media='all' />
	<link rel='stylesheet' id='buttons-css' href='http://●●●●/wp-includes/css/buttons.min.css?ver=6.2.2' type='text/css' media='all' />
 ・・・・(以下略)・・・・

CSSファイルのパスを見ると分かります。SCHEME 部分が https ではなく http になっていました。
先に誤解なきようお伝えしますが、 インストール画面のURLは「https://●●●●/wp-admin/install.php」です。

https でアクセスしてもHTMLソース中に http で記載があれば、それは混在コンテンツとなります。
セキュリティー上、問題があると判断され、通常はブラウザー側でアクセスが拒否されます。

つまりCSSが読み込まれなかったため、画面崩れが起きたわけです。

では、なぜ https ではなく http に置き換わってしまったのでしょう。
というのをこの後追っかけていきます。

grep、grep、grep ‥

http と書いている箇所を特定すれば、原因が明確になると思い、ソースコードをただひたすら追っかけていく、という事をしました。地味でめんどくさい作業なんですが、これが出来ないとエンジニアとしては失格です。
根本解決をすることをせず、表層的な原因と解決方法を求める人は、答えを提示している人を探す事には長けていますが、答えが無い場合に行き詰まります。
‥とまあ、エンジニア向けの説教はこの程度にして。

やったことは、調査に必要なキーワードを決めて、どのプログラムにキーワードが書かれているかを調べていく、という事の繰り返しです。こうして原因箇所を特定していきます。

grep は Linux をはじめとする UNIX系OSで用意されている標準ツールで、複数あるファイルから探したい文字列を見つけ出してくれる、とっても便利なツールです。

今回は、 css のパスがおかしいという事で、そのパスに含まれる「dashicons-css」を手始めに grepする事にしました。
記録は残していませんが、流れとしては

 該当ファイルが見つかる → それを定義している関数名を特定 → その関数で grep して呼び出し元のプログラムを特定する

このような繰り返しです。

そうして set_url_scheme() という関数に行きついたときに、このような処理を見つけました。

function set_url_scheme( $url, $scheme = null ) {		
	$orig_scheme = $scheme;	
		
	if ( ! $scheme ) {	
		$scheme = is_ssl() ? 'https' : 'http';
	} elseif ( 'admin' === $scheme || 'login' === $scheme || 'login_post' === $scheme || 'rpc' === $scheme ) {	
		$scheme = is_ssl() || force_ssl_admin() ? 'https' : 'http';
	} elseif ( 'http' !== $scheme && 'https' !== $scheme && 'relative' !== $scheme ) {	
		$scheme = is_ssl() ? 'https' : 'http';
	}	
・・・・(以下略)・・・・

http か https を記述している部分はここでした。
ではその判定をしている「is_ssl()」関数とは?

PHP標準関数かな?それとも‥ と思いググったところ、 WordPressドキュメントに行きつきました。

そこで原因と対処が明確になりました。

結論:$_SERVER[‘HTTPS’]の値を直してください

WordPressが提供するドキュメントには、具体的な解決方法が提示されていました。

if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https')
$_SERVER['HTTPS'] = 'on';

これを、wp-config.php の先頭に書いてください。

上のドキュメントには、コードの直前に「wp-includes/load.php」と書いていますが、その前に「Websites behind load balancers or reverse proxies that support HTTP_X_FORWARDED_PROTO can be fixed by adding the following code to the wp-config.php file, above the require_once call:」と書いてますのでお間違え無きよう。。誤記でしょうが紛らわしい😅。

こちらは本来表示されるはずの画面。
上で wp-config.phpファイルを先に作る事になったので、実際にはこの次の画面が表示されましたが、もう崩れる事はありませんでした。インストールも無事終わり、管理画面へのログインなど、その後の動作は全て順調でした。

WordPressはLoad Balancer や Reverse-Proxyと相性が悪い

ここからは、どちらかというとエンジニアや技術的なところに興味がある方向けの文章となりますので、非エンジニアの方であれば読み飛ばしていただいて大丈夫です。

前述の is_ssl()関数のドキュメントや、そこから紹介されている記事に書かれていますが、WordPress関数の is_ssl()は、いくつかのロードバランサー、特にレンタルサーバー屋が提供しているところだとうまく動かない、と明言しています。

is_ssl()関数のソースコードを見ると分かるのですが、ここでは $_SERVER[‘HTTPS’]というグローバル変数を使って リクエストが http か https かを判断しています。
$_SERVER[‘HTTPS’]だけだと心もとないのか、$_SERVER[‘SERVER_PORT’] の値もみていますが、これでも判断できない場合に、 is_ssl()関数は http だと判断しています。

この判断だと足りない事があるから、上記コード $_SERVER[‘HTTP_X_FORWARDED_PROTO’] の有無を見て、それが https であれば $_SERVER[‘HTTPS’] 変数に’on ‘ を強制セットする、という力技を行ってください、とアドバイスされています。

$_SERVER変数の説明

ここで、何度か出てきた $_SERVER変数について説明します。

WordPressが動作するプログラミング言語 PHP では、$_SERVER という連想配列が定義されています。
そこにはウェブサーバーに関する様々な情報が格納されています。

PHP: $_SERVER – Manual

こちらに詳しく書いていますので、ご興味あれば読んでみてください。
もしこの記事を読んでいるのがプログラマーさんであれば、読んでしかるべきところです😊

https://www.en-pc.jp/tech/php_server/
手前味噌ですが、こちらにも$_SERVER変数の説明と実際の値を例示していますので、よろしければご覧ください。

今回出てきた変数について、更に説明を続けます。
説明の中でどのように今回の問題を解決したのか、その背景(土台)となるインフラや通信に関する仕様などについても触れておりますので、プログラマーさんやエンジニアさんはぜひご一読下さい。

$_SERVER[‘HTTPS’]

「スクリプトが HTTPS プロトコルを通じて実行されている場合に 空でない値が設定されます。」
との事です。

そう書いてはありますが、どうやって HTTPSプロトコルかを判断しているのか、には言及していません。

「これらの変数のほとんどは、 » CGI/1.1 specification で定義されています。」
という記載もありましたが、リンク先をみても載っていない。

そう思いさらにググったところ、同じ疑問を持たれている方がおりました。
PHP の $_SERVER[‘HTTPS’] が on になる流れ 〜Apache mod_php 編〜 #PHP – Qiita

さすがに PHPのソースコードを追っかける気力はなかったので、これは助かります(笑)

どうやら、Apache + mod_ssl 構成時に定義される環境変数「HTTPS」を参照しているようです。

なるほどね、と思いつつ別の疑問が出てきました。
Apacheを使わない場合はどうなるんだろう?

$_SERVER[‘SERVER_PORT’]

Apache を使っていない場合、例えば Nginx の場合に HTTPS環境変数を設定しているのか?設定できるのか?といった疑問です。そういうのもあり、おそらくですが WordPress開発者は環境変数「SERVER_PORT」も見るようにしたのでしょう。

この環境変数は、前述の CGI/1.1 specification にも記載されていますからNginxでもおそらく対応させている事でしょう。様々なウェブサーバーに対応できるようにWordPress側でもこの変数を使うよう対応させたのでしょう。

でもまだ疑問は残ります。
ロードバランサーがある環境ではうまくいかないのでは?

ロードバランサーでは、ウェブサーバーの負荷分散というお仕事をしています。
ロードバランサーでリクエストを受け付け、それを背後に複数控えているウェブサーバーに均等にリクエストを割り振って、1台当たりの負荷を分散させる、という事をしています。

もう1つ、ロードバランサーの役割としてよく行われているのは、https で必要な暗号化/復号化という負荷のかかる処理を一手に引き受けるというものです。配下のウェブサーバーには http (暗号化しない状態:平文)でリクエストを引き継ぐので、さらに負荷を分散できる、というものです。

この場合、配下のウェブサーバー上で動いているWordPressには http でリクエストが来ます。
つまり、環境変数「SERVER_PORT」は 80という値がセットされてしまいます。
$_SERVER[‘SERVER_PORT’] をつかっても、HTTPS通信しているかどうかを判断できない場合があるのです。

WordPress の is_ssl()関数ではここまでしか行っていません。

$_SERVER[‘HTTP_X_FORWARDED_PROTO’]

こちらの変数は、前述の結論部分で提示したコードで使われています。

この変数は、ロードバランサーなどで受け取ったリクエストを、背後にあるサーバーに引き継ぐときに追加で発行するリクエストヘッダーの一つです。ちなみに、FORWARDED というのはフォワード、つまりIT用語で「転送する」という意味があります。言葉の意味を知っておくと理解が深まると思いますので、一応言っておきます😊

もう1つ、Reverse-Proxyは、ロードバランサーなどのベースとなる技術です。
HTTP_X_FORWARDED_PROTOはロードバランサーに限らず、その元となる Reverse-Proxyがあったとしても付与される追加ヘッダーですので、こちらもぜひ覚えておいてください。

この変数がセットされている、という事は、ロードバランサーや Reverse-Proxy を経由してリクエストが来ている、という事です。変数値をみれば、大元のリクエストが https かどうか、というのもわかります。

これを使って HTTPSかどうかを判断するようにし、その結果を is_ssl()関数で使っている $_SERVER[‘HTTPS’]にセットする事で、 is_ssl()関数は https を変すようになるわけです。

とまあ、そこまでは分かったのですが。

is_ssl()関数で ロードバランサーのことまで配慮してくれたらいいのに

と、思ってしまいました。

何か理由があるのですかね?
コアなところに手を入れて、予期せぬ影響が出るのを避けてるのでしょうか?

まあ、ここまで大きな仕組みで、かつ世の中で広く使われているものですからね。
色んなところに配慮しなければいけない、という事情もあるのでしょう。

少なくとも現状としては、環境依存の問題は各々が解決して下さい、という事なのだろうと思います。

ConoHaサーバーの環境を改めて見てみる

今回問題が起きたのは、ConoHa というレンタルサーバーです。
このサーバー上で $_SERVER 変数に何がセットされているのだろうと思い、改めて確認してみました。

$_SERVER 変数を var_dump した結果の抜粋です。

$_SERVER[‘HTTPS’] は無い。
[“SERVER_PORT”]=> string(4) “8080” となっている。

[“HTTP_X_FORWARDED_PROTO”]=> string(5) “https”
[“HTTP_X_FORWARDED_HOST”]=> string(14) “●●●●”
[“HTTP_X_FORWARDED_PORT”]=> string(3) “443”

こんなかんじです。Reverse Proxy サーバーを経由している、という事が分ります。

PORT番号が8080なので、おそらく WordPressが動いているのは、php-fpm あたりなんだろうな、という気がしています。

ConoHaが特殊な構成をしている、という話ではないのでしょうが、やはり利用書が多いのでしょう、検索した時にいくつかConoHaの話題がでていたようです。

もともとこの話は、社員さんからの質問でした。社員さんが分からないからと「Conoha WordPress 手動インストール」といったキーワードでググったらしいのですが、うまくいかない事例がいくつか出てきたそうです。

詳しくは聞いてはいませんが、いくつか面白い解決方法を提示しているところがあったそうです。
例えば自動インストールをして出来たコピーを使いまわす、とか。
‥それを書いた人も、分からないなりに頑張ったんだろうな、というところは認めたいと思います。

ですが、その方法は根本解決ではありませんから、きっとその後も何かしら問題を起こしているのかもしれませんね。

困ったら、IT相談役が相談に乗ります

今回起きた問題はうちの社員さんから来たリクエストですが、今の私のやりたいことは、このような事に対応できるエンジニアさんを、内外問わず増やしていくことです。

IT相談役、というサービスを始めた理由の一つでもあります。

ご興味あればご覧ください。
今回の場合であれば、社内エンジニアの育成や、ホームページ制作業者さんやマーケティング会社さんなど、ITを取り扱うけれども技術職ではない人たちに技術的サポートをする、という事が出来ると思っています。

正直破格だと思っていますし、期待した内容でなければすぐ解約も出来ますので、まずはお気軽にご相談いただけたら幸いです。

関連記事

メールマガジン「ビジネスお役立ち情報」のご案内

あなたのビジネスに役に立つ情報は要りませんか?技術動向、マーケティング、AI、業務効率化、セキュリティ、法務、その他。エン・PCサービスならではの情報をお届けします。

登録解除はいつでも行えます。
まずはお気軽にご登録ください。

ブログ一覧へ
URLをコピーする