nginxでproxy_passにドメイン名を設定する場合の注意点

PSP株式会社の村田です。

今回はnginxでReverese Proxyを行う際に、proxy先がドメイン名の場合にハマったポイントがあり、私が見る限りではnginxのドキュメントにも書かれていない仕様があったのでご紹介したいと思います。

はじめに

まずは今回ハマった事象を説明する前に、今回の件と関連するnginxのよくあるハマりポイントの2つを紹介したいと思います。

  1. proxy_passに設定するURL末尾のスラッシュの有無による挙動の変化

  2. proxy_passのURLをドメイン名で設定した場合、名前解決を行うのは起動時のみ

proxy_passに設定するURL末尾のスラッシュの有無による挙動の変化

proxy_passに設定するURLの末尾、いわゆるTrailing Slashの有無によって、proxy先のURLが変わります。

# Trailing Slashなし
location /foo/ {
    proxy_pass http://192.168.0.1;
}

# Trailing Slashあり
location /foo/ {
    proxy_pass http://192.168.0.1/;
}

Trailing Slashなしの場合、http://127.0.0.1/foo/bar へのアクセスは、http://192.168.0.1/foo/bar にproxyされます。

Trailing Slashありの場合、http://127.0.0.1/foo/bar へのアクセスは、http://192.168.0.1/bar にproxyされます。

proxy_passにドメイン名で設定した場合、名前解決を行うのは起動時のみ

location /foo/ {
    proxy_pass https://www.example.com;
}

上記の設定の場合、nginxはTTLとは関係なく起動時(もしくはreload時)にしか名前解決を行いません。
なのでドメインに紐づくIPを変更したり、DNSラウンドロビンを行っているような場合はこの設定ではうまくいきません。

定期的に名前解決を行うためにはsetディレクティブで変数にドメインかURLをセットし、proxy_passでその変数を展開するようにすることで、定期的に名前解決することが出来るようになります。
これはnginxのドキュメントにも記載されています。
http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass

location /foo/ {
    resolver 8.8.8.8;
    set $url https://www.example.com/;
    proxy_pass $url;
}

また余談ですが、proxy_passに直接ドメイン名を書いた場合は、OSで設定されているDNSサーバ(もしくは/etc/hosts)で名前解決を行い、setディレクティブを使用した場合はresolverディレクティブで指定されたDNSサーバで名前解決が行われ、OSで設定されているDNSサーバや/etc/hostsは参照されません。

今回ハマったポイント

http://127.0.0.1/foo/bar へのアクセスを https://www.example.com/bar にproxyするような設定を行う必要がありました。
私はnginxの上記2点の仕様は把握していたので、以下の設定を試してみたのですが、実際には http://127.0.0.1/foo/bar に対するアクセスは https://www.example.com/ にproxyされてしまい想定通りの動きにはなりませんでした。

location /foo/ {
    resolver 8.8.8.8;
    set $url https://www.example.com/;
    proxy_pass $url;
}

proxy_passに変数が含まれる場合の仕様

実はnginxにはproxy_passに変数が含まれている、かつTrailing Slashありの場合、リクエストURLのパスおよびGETパラメータがProxy先に引き継がれないという動きになっていました。

# 変数あり・Trailing Slashなし
location /foo/ {
    resolver 8.8.8.8;
    set $url https://www.example.com;
    proxy_pass $url;
}

# 変数あり・Trailing Slashあり
location /foo/ {
    resolver 8.8.8.8;
    set $url https://www.example.com/;
    proxy_pass $url;
}

変数あり・Trailing Slashなしの場合、http://127.0.0.1:80/foo/bar へのアクセスは、 https://www.example.com/foo/bar にproxyされます。

変数あり・Trailing Slashありの場合、http://127.0.0.1:80/foo/bar へのアクセスは、 https://www.example.com/bar ではなく、 https://www.example.com/ にproxyされます。

最終的な解決方法

正規表現を使用して、/foo/以下のパスとGETパラメータをurlと結合することで解決出来ました。

location ~ /foo/(.*) {
    resolver 8.8.8.8;
    set $url https://www.example.com/;
    proxy_pass $url$1$is_args$args;
}

この設定で、http://127.0.0.1/foo/bar へのアクセスを https://www.example.com/bar にproxyさせることが出来るようになりました。

まとめ

今回紹介したnginxのReverse Proxyの挙動をまとめてみました。

# Trailing Slashなし
location /foo/ {
    proxy_pass http://192.168.0.1;
}
http://127.0.0.1/foo/bar → http://192.168.0.1/foo/bar

# Trailing Slashあり
location /foo/ {
    proxy_pass http://192.168.0.1/;
}
http://127.0.0.1/foo/bar → http://192.168.0.1/bar

# 変数あり・Trailing Slashなし
location /foo/ {
    resolver 8.8.8.8;
    set $url https://www.example.com;
    proxy_pass $url;
}
http://127.0.0.1/foo/bar → https://www.example.com/foo/bar

# 変数あり・Trailing Slashあり
location /foo/ {
    resolver 8.8.8.8;
    set $url https://www.example.com/;
    proxy_pass $url;
}
http://127.0.0.1/foo/bar → https://www.example.com/

# 変数あり・Trailing Slashあり・正規表現
location ~ /foo/(.*) {
    resolver 8.8.8.8;
    set $url https://www.example.com/;
    proxy_pass $url$1$is_args$args;
}
http://127.0.0.1/foo/bar → https://www.example.com/bar