問題
下記の構成で 2 つのバーチャルホスト (host1.example.com, host2.example.com) を作成し,同一ホストで異なるサービスを提供しようとした.しかし動作させてみると,クライアントがリクエストしたホスト名に関係なく host1.example.com からレスポンスが来てしまう(host1 にアクセスしても host2 にアクセスしても,どちらも host1 にアクセスしたことになる).
[client] ---------- [apache] ---------- [nginx] -----+---- [unicorn1 (host1.example.com)]
(192.168.0.10) (192.168.0.20) |
`--- [unicorn2 (host2.example.com)]
Apache の設定
host1.example.com および host2.example.com 宛のリクエストはすべて http://192.168.0.20:80/ に送る.
<VirtualHost *:80>
ServerName host1.example.com
ProxyPass / http://192.168.0.20:80/ retry=3
ProxyPassReverse / http://192.168.0.20:80/
</VirtualHost>
<VirtualHost *:80>
ServerName host2.example.com
ProxyPass / http://192.168.0.20:80/ retry=3
ProxyPassReverse / http://192.168.0.20:80/
</VirtualHost>
nginx (host1) の設定
host1.example.com 宛のリクエストを処理する.
upstream unicorn_server {
server unix:/tmp/unicorn.sock fail_timeout=0;
}
server {
listen 80;
server_name host1.example.com;
root /var/www/app/current/public;
location / {
if (!-f $request_filename) {
proxy_pass http://unicorn_server;
break;
}
}
}
nginx (host2) の設定
host2.example.com 宛のリクエストを処理する.
upstream unicorn_test_server {
server unix:/tmp/unicorn_test.sock fail_timeout=0;
}
server {
listen 80;
server_name host2.example.com;
root /var/www/app/current/public;
location / {
if (!-f $request_filename) {
proxy_pass http://unicorn_test_server;
break;
}
}
}
原因
Apache から nginx に「クライアントがリクエストしたホスト名 (host1, host2)」が送られていないために想定外の動作をしていた.
Apache proxy は,デフォルトでは Host ヘッダを保持せず,ターゲットサーバのホスト名に基づいた Host ヘッダを代わりに生成する.先述の設定では, Apache は host1 宛と host2 宛の双方のリクエストを,ホスト名ではなく IP アドレス (192.168.0.20) を指定して送っていた.そのため, nginx がリクエストを受け取った時点で host1 や host2 というホスト名の情報が欠落していた.
nginx はリクエスト先のホスト名に基づいて処理を分ける設定となっている.しかし Apache から送られてくるリクエストには host1 や host2 といったホスト名が含まれず IP アドレスしか存在しない.そのため,ホスト別の処理を実行できず,先に設定が読み込まれる host1 のレスポンスのみを返していた.
+---+ +---+ +---+
| | to:host1 | | to:192.168.0.20 | |
| c | --------------------> | A | --------------------> | |
| l | <-------------------- | p | <-------------------- | n |
| i | from:192.168.0.20 | a | from:192.168.0.20 | g |
| e | | c | | i |
| n | to:host2 | h | to:192.168.0.20 | n |
| t | --------------------> | e | --------------------> | x |
| | <-------------------- | | <-------------------- | |
| | from:192.168.0.20 | | from:192.168.0.20 | |
+---+ +---+ +---+
解決方法
Apache から nginx に「クライアントがリクエストしたホスト名」が送られるようにするため, Apache の設定ファイルに ProxyPreserveHost On
を追記する.
<VirtualHost *:80>
ServerName host1.example.com
ProxyPass / http://192.168.0.20:80/ retry=3
ProxyPassReverse / http://192.168.0.20:80/
ProxyPreserveHost On
</VirtualHost>
<VirtualHost *:80>
ServerName host2.example.com
ProxyPass / http://192.168.0.20:80/ retry=3
ProxyPassReverse / http://192.168.0.20:80/
ProxyPreserveHost On
</VirtualHost>
ProxyPreserveHost を有効 (On) にすると, Apache がターゲットサーバに送信するプロキシリクエストを作成する際に,クライアントの元の Host ヘッダが保持される.これで nginx はクライアントがリクエストしたホスト名 (host1 もしくは host2) を知ることができるため,ホスト別の処理を実行できるようになる.
+---+ +---+ +---+
| | to:host1 | | to:host1 | |
| c | --------------------> | A | --------------------> | |
| l | <-------------------- | p | <-------------------- | n |
| i | from:host1 | a | from:host1 | g |
| e | | c | | i |
| n | to:host2 | h | to:host2 | n |
| t | --------------------> | e | --------------------> | x |
| | <-------------------- | | <-------------------- | |
| | from:host2 | | from:host2 | |
+---+ +---+ +---+
おわりに
(多段)プロキシを構築していると通信内容(リクエスト,レスポンス)を把握しづらくなるため注意が必要である.