ぶていのログでぶログ

思い出したが吉日

NGINXのリクエストの処理はserver_nameよりlistenが優先される

NGINXを使って運用しているWebサーバで、設定を変更したあとに動作検証として curl --resolve example.com:80:127.0.0.1 http://example.com みたいにlocalhostへのリクエストを行ったら、意図しない挙動をしてハマったのでメモ。 タイトル通りなのだけど、 listen localhost:80 なserver定義があってそちらにルーティングされていたために意図しない挙動になっていた。 具体的には以下のような設定をしていた(説明に必要な部分のみ抽出している)。

server {
    listen localhost:80;
    server_name localhost;
    location / { return 200 "localhost"; }
}

server {
    listen 80 default_server;
    server_name _;
    location / { return 200 "default_server"; }
}

一見、default_serverが返却されそうだが、先のコマンドだと localhostが返ってくる。 では、default_serverなルーティングを利用するにはどうしたらいいか?答えは簡単でlocalhost以外を使えば良い。 curl --resolve example.com:80:<localhost以外のIP> http://example.com

実際にDockerを用いて確認してみる。 まず上記で示した設定をnginx.confとして保存する。 次にnginxコンテナイメージを使ってコンテナを起動する。

$ docker run --rm -d --name nginx -v $(pwd)/nginx.conf:/etc/nginx/conf.d/default.conf nginx

次にlocalhostに対してexample.comへのリクエストを発行する。 結果としてlocalhostが表示されるはず。

$ docker exec nginx curl -s --resolv example.com:80:127.0.0.1 http://example.com
localhost

次にlocalhost以外のIPを指定してみる。 --resolvにはホスト名を指定できないのでIPはhostsから取ってきている。

$ NGINX_IP=$(docker exec nginx tail -1 /etc/hosts | cut -f1)
$ docker exec nginx curl -s --resolv example.com:80:$NGINX_IP http://example.com
default_server

localhost以外のIPを指定すると意図した挙動になるはず。

なお、この挙動はHow nginx processes a requestに記述されている。

In this configuration, nginx first tests the IP address and port of the request against the listen directives of the server blocks. It then tests the “Host” header field of the request against the server_name entries of the server blocks that matched the IP address and port. If the server name is not found, the request will be processed by the default server. この設定では、nginxはまずリクエストのIPアドレスとポートをサーバーブロックのlistenディレクティブと照合します。その後、リクエストの「Host」ヘッダーフィールドを、IPアドレスとポートが一致したサーバーブロックのserver_nameエントリと照合します。サーバー名が見つからない場合、リクエストはデフォルトサーバーによって処理されます。