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
```

:thinking:

# 原因：繰り返しリダイレクトが行われている
↑ブラウザのエラーからhtaccess周りの設定かなと思いましたが、nginxだしなーとググれば答えが見つかった。

## do_action('template_redirect') をコメントアウト
[WordPressで無限リダイレクトが発生したときの対策と調査メモ](http://did2memo.net/2016/01/26/wordpress-redirect-loop/)
>「wp-includes/template-loader.php」の12行目にある「do_action('template_redirect')」をコメントアウト（行頭に「//」を追加）しました。

``` diff: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 [（引用元）](https://core.trac.wordpress.org/ticket/35012)

4.4.1で既に解決済みらしい。（Wordpressの使用バージョンは4.8.1）:thinking:

## remove_action に redirect_canonical を追加

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

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

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

## リダイレクトが起こっている本当の原因
[リバースプロキシを使っているとWORDPRESSで記事の参照がループしてしまう問題について](http://www.l2tp.org/archives/5288)

>パーマリンクを元に作成された、記事自体のパーマリンク。リダイレクトしたい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_home` は `home_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行目に書いてます。

* [get_home_url:WordPress私的マニュアル](https://elearn.jp/wpman/function/get_home_url.html)


## 解決策
すごく単純

:frowning2:

``` diff: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.php` で `home_url` と `site_url` を上書きするーというやり方が本来の方法の様だったが、
httpsや複雑な処理を実装する予定も今回なく、 `proxy_set_header  Host `に直接 `$http_host`に設定しました。

上記方法でホスト名＋ポート番号を取得できれば、上記 [解決策redirect_url-を書き換える](https://qiita.com/arima7th/items/b023acd3ef4bd8989568#解決策redirect_url-を書き換える) は当然不要になりました。


# 引用元
* [WordPressで無限リダイレクトが発生したときの対策と調査メモ](http://did2memo.net/2016/01/26/wordpress-redirect-loop/)
* [リバースプロキシを使っているとWORDPRESSで記事の参照がループしてしまう問題について](http://www.l2tp.org/archives/5288)

# 参考
* [Fix wordpress 301 loop when enable nginx…](https://blog.gpjtag.com/?p=944)