nginx と Apache でリバースプロキシした際に Wordpress でリダイレクトループした際の対処法


検証環境
client <---> nginx(http:80) <---> apache(http:8080)

症状:Wordpress設置後ページが正常に表示されない

ディレクトリ構成は/var/www/html/wp
テーマ依存ではなく、Wordpress設置後のみに起こる症状でした。

Chromeさん

このページは動作していません

localhost でリダイレクトが繰り返し行われました。
Cookie を消去してみてください.
ERR_TOO_MANY_REDIRECTS

--
JS error http://localhost:8000/ net::ERR_TOO_MANY_REDIRECTS

原因:繰り返しリダイレクトが行われている

↑ブラウザのエラーからhtaccess周りの設定かなと思いましたが、nginxだしなーとググれば答えが見つかった。

do_action('template_redirect') をコメントアウト

WordPressで無限リダイレクトが発生したときの対策と調査メモ

「wp-includes/template-loader.php」の12行目にある「do_action('template_redirect')」をコメントアウト(行頭に「//」を追加)しました。

wp-includes/template-loader.php

-   do_action( 'template_redirect' );
+   //do_action( 'template_redirect' );

↑のコメントアウトで確かにリダイレクトはなくなり、めでたく正常にWordpressが表示されました!
wp-includes/query.php の4986行目 wp_old_slug_redirect が原因だったようです。

が、

Milestone changed from Awaiting Review to 4.4.1 (引用元)

4.4.1で既に解決済みらしい。(Wordpressの使用バージョンは4.8.1)

remove_action に redirect_canonical を追加

さらにググると redirect_canonical が原因でループが起こってるようでした。
テーマ内のfunctions.phpに下記コード追加だけでループは止まり正常に表示されます!

functions.php
remove_action('template_redirect', 'redirect_canonical');

ですが根本の原因がサッパリわかりません!
っというかこのアクション消して本当に大丈夫なのかという不安感たっぷりです。

リダイレクトが起こっている本当の原因

リバースプロキシを使っているとWORDPRESSで記事の参照がループしてしまう問題について

パーマリンクを元に作成された、記事自体のパーマリンク。リダイレクトしたいURLを redirect_url と呼ぶ。
リダイレクトされる前の、実際に今アクセスされているURLを requested_url とする。
この二つが一致しないので延々とredurect_urlにリダイレクトさせようとしてしまっている。

473行目で $redirect_url$requested_url が一致しない場合 return を返してます。

wp-includes/canonical.php
function redirect_canonical( $requested_url = null, $do_redirect = true ) {

    

    if ( ! $redirect_url || $redirect_url == $requested_url ) {
        return;
    }

これを回避すれば良いようなので、それぞれvar_dumpしてみると、

string(22) "http://localhost:8000/" //$redirect_url
string(17) "http://localhost/"      // $requested_url

 
 
_人人人人人人人人人_
> ポート番号!! <
 ̄Y^Y^Y^Y^Y^Y^Y^Y ̄

 
 

じつはこのrequested_urlは$_SERVERを元に作成される。サーバ環境変数。つまり見せかけ上のURL(ドメイン名)と実際に>PHPが動いているドメイン名が違っていると無限ループする、というわけだ。
この見せかけ上のドメインとPHPが動いているサーバのドメイン名が違う、というのはリバースプロキシを使っていると起こってしまう。

つまり、
379行目で $user_homehome_url()から$redirect_urlにURLを代入してますが、

wp-includes/canonical.php
function redirect_canonical( $requested_url = null, $do_redirect = true ) {

    

    $user_home = @parse_url(home_url());
    if ( !empty($user_home['host']) )
        $redirect['host'] = $user_home['host'];
    if ( empty($user_home['path']) )
        $user_home['path'] = '/';

--
    var_dump($user_home);
    
    array(4) {
        ["scheme"]=> string(4) "http"
        ["host"]=> string(9) "localhost"
        ["port"]=> int(9010)
        ["path"]=> string(1)"/"
    }

62行目で $requested_url$_SERVER['HTTP_HOST']$_SERVER['REQUEST_URI'] でURLを形成していました。

wp-includes/canonical.php
function redirect_canonical( $requested_url = null, $do_redirect = true ) {

    

    if ( ! $requested_url && isset( $_SERVER['HTTP_HOST'] ) ) {
        // build the URL in the address bar
        $requested_url  = is_ssl() ? 'https://' : 'http://';
        $requested_url .= $_SERVER['HTTP_HOST'];
        $requested_url .= $_SERVER['REQUEST_URI'];
    }

この見せかけ上のドメインとPHPが動いているサーバのドメイン名が違う、というのはリバースプロキシを使っていると起こってしまう。

解決策:redirect_url を書き換える

functions.php
// nginx と Apache で リバースプロキシ した際に カノニカルURL が不一致するバグ対策
function change_ridirect_url($redirect_url) {
    $redirect_url = is_ssl() ? 'https://' : 'http://';
    $redirect_url .= $_SERVER['HTTP_HOST'];
    $redirect_url .= $_SERVER['REQUEST_URI'];
    return $redirect_url;
}
add_action('redirect_canonical', 'change_redirect_url', 10);

nginx側で回避できるのかと思いますが、
フロントのコーディングばかりでサーバーの知識が乏しいので、
今回はこんな感じで、テーマ内の functions.php に追記してリダイレクト回避しました。

追記

Twentysevenで表示確認行っていたので気付かなかったのですが、実際に上記コードでオリジナルテーマを開発しようとすると、
home_url() が返す値も $_SERVER['HTTP_HOST'] を返していました。(site_url()
ポート番号まで $_SERVER['HTTP_HOST'] で返してもらわないと、今までの資産(自作の関数群・プラグインでhome_url()を使ってる)が使えないので、
コメントでも頂きましたがnginx側でホスト名+ポート番号を返してもらう必要がありました。
home_url() の中身は wp-includes/link-template.php の 2968行目に書いてます。

解決策

すごく単純

default.conf
- proxy_set_header  Host               $host;
+ proxy_set_header  Host               $http_host;

nginx側の設定で$host としているとリバースプロキシではポート番号まで返さないので $http_host としてあげると、
$_SERVER['HTTP_HOST'] でホスト名+ポート番号までを送ってくれます。

また、
Hostではなく、プロキシサーバのホスト名 X-Forwarded-Server

proxy_set_header  X-Forwarded-Server               $http_host;

として、
$_SERVER['HTTP_X_FORWARDED_FOR'] でホスト名+ポート番号を取得して、
wp-config.phphome_urlsite_url を上書きするーというやり方が本来の方法の様だったが、
httpsや複雑な処理を実装する予定も今回なく、 proxy_set_header Hostに直接 $http_hostに設定しました。

上記方法でホスト名+ポート番号を取得できれば、上記 解決策redirect_url-を書き換える は当然不要になりました。

引用元

参考