nginx のバーチャルホストとポート番号 ～server_name にポート番号はつけるな～

# 今回の検証環境

* Amazon EC2 t2.micro
* Ubuntu Server 24.04 LTS (HVM), SSD Volume Type (ami-075686beab831bb7f)
* nginx/1.24.0 (Ubuntu)

# server_name にポート番号をつけると効かない

[`server_name`](https://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) を用いると、バーチャルサーバーの名前を指定し、リクエストの処理方法を [`Host` ヘッダー](https://developer.mozilla.org/ja/docs/Web/HTTP/Reference/Headers/Host)の内容に応じて変えることができる。

`Host` ヘッダーの内容には、ポート番号が含まれる場合がある。(`example.com:8080` など)
なので、`server_name` もポート番号を含めて指定したほうが良さそうに思える。
早速やってみよう。

```nginx
server {
	listen 8080;
	server_name 127.0.0.1:8080;
	return 200 "using ip address\n";
}

server {
	listen 8080;
	server_name localhost:8080;
	return 200 "using host name\n";
}
```

`curl` を用いてリクエストを送ってみる。

```text
YUKI.N> curl -v http://127.0.0.1:8080/
*   Trying 127.0.0.1:8080...
* Connected to 127.0.0.1 (127.0.0.1) port 8080
> GET / HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/8.5.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: nginx/1.24.0 (Ubuntu)
< Date: Sat, 26 Apr 2025 23:53:02 GMT
< Content-Type: application/octet-stream
< Content-Length: 17
< Connection: keep-alive
<
using ip address
* Connection #0 to host 127.0.0.1 left intact
```

```text
YUKI.N> curl -v http://localhost:8080/
* Host localhost:8080 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:8080...
* connect to ::1 port 8080 from ::1 port 50870 failed: Connection refused
*   Trying 127.0.0.1:8080...
* Connected to localhost (127.0.0.1) port 8080
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/8.5.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: nginx/1.24.0 (Ubuntu)
< Date: Sat, 26 Apr 2025 23:53:16 GMT
< Content-Type: application/octet-stream
< Content-Length: 17
< Connection: keep-alive
<
using ip address
* Connection #0 to host localhost left intact
```

`http://127.0.0.1:8080/` にアクセスした場合も `http://localhost:8080/` にアクセスした場合も、両方とも `http://127.0.0.1:8080/` 用のメッセージ `using ip address` が出力されており、`server_name` の指定が**意図通りに反映されていない**ことがわかる。

# server_name にポート番号をつけないと効く

`server_name` にポート番号込みの[ホスト](https://developer.mozilla.org/ja/docs/Web/API/URL/host)を指定してもなぜか上手く反映されないようなので、`server_name` からポート番号を外してみる。

```nginx
server {
	listen 8080;
	server_name 127.0.0.1;
	return 200 "using ip address\n";
}

server {
	listen 8080;
	server_name localhost;
	return 200 "using host name\n";
}
```

同様にリクエストを送ってみる。

```text
YUKI.N> curl -v http://127.0.0.1:8080/
*   Trying 127.0.0.1:8080...
* Connected to 127.0.0.1 (127.0.0.1) port 8080
> GET / HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/8.5.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: nginx/1.24.0 (Ubuntu)
< Date: Sun, 27 Apr 2025 00:04:53 GMT
< Content-Type: application/octet-stream
< Content-Length: 17
< Connection: keep-alive
<
using ip address
* Connection #0 to host 127.0.0.1 left intact
```

```text
YUKI.N> curl -v http://localhost:8080/
* Host localhost:8080 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:8080...
* connect to ::1 port 8080 from ::1 port 48512 failed: Connection refused
*   Trying 127.0.0.1:8080...
* Connected to localhost (127.0.0.1) port 8080
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/8.5.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: nginx/1.24.0 (Ubuntu)
< Date: Sun, 27 Apr 2025 00:04:55 GMT
< Content-Type: application/octet-stream
< Content-Length: 16
< Connection: keep-alive
<
using host name
* Connection #0 to host localhost left intact
```

`http://127.0.0.1:8080/` にアクセスした際は `using ip address` が、`http://localhost:8080/` にアクセスした際は `using host name` が出力され、`server_name` の指定が**意図通りに反映されている**ことがわかる。

# Host のポートではなく TCP のポートで判定される

まず、2種類のポートでアクセスを受け付け、それぞれ別のレスポンスを返すように設定する。

```nginx
server {
	listen 8080;
	listen 11111;
	server_name "";
	return 200 "using default";
}

server {
	listen 8080;
	server_name 127.0.0.1;
	return 200 "using ip address on port 8080\n";
}

server {
	listen 8080;
	server_name localhost;
	return 200 "using host name on port 8080\n";
}

server {
	listen 11111;
	server_name 127.0.0.1;
	return 200 "using ip address on port 11111\n";
}

server {
	listen 11111;
	server_name localhost;
	return 200 "using host name on port 11111\n";
}
```

SSHの機能でポート転送を行い、nginx が TCP 接続を受信するのとは違うポートでリクエストを行えるようにする。

![ローカルのポート11111をリモートのポート8080に、ローカルのポート22222をリモートのポート11111に転送する](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/297393fb-69d4-4256-bf0c-8ed7052a49d8.png)

実際にリクエストを行ってみる。

```text
YUKI.N>curl -v http://127.0.0.1:11111/
*   Trying 127.0.0.1:11111...
* Connected to 127.0.0.1 (127.0.0.1) port 11111
* using HTTP/1.x
> GET / HTTP/1.1
> Host: 127.0.0.1:11111
> User-Agent: curl/8.11.1
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 200 OK
< Server: nginx/1.24.0 (Ubuntu)
< Date: Sun, 27 Apr 2025 00:18:24 GMT
< Content-Type: application/octet-stream
< Content-Length: 30
< Connection: keep-alive
<
using ip address on port 8080
* Connection #0 to host 127.0.0.1 left intact
```

```text
YUKI.N>curl -v http://127.0.0.1:22222/
*   Trying 127.0.0.1:22222...
* Connected to 127.0.0.1 (127.0.0.1) port 22222
* using HTTP/1.x
> GET / HTTP/1.1
> Host: 127.0.0.1:22222
> User-Agent: curl/8.11.1
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 200 OK
< Server: nginx/1.24.0 (Ubuntu)
< Date: Sun, 27 Apr 2025 00:18:31 GMT
< Content-Type: application/octet-stream
< Content-Length: 31
< Connection: keep-alive
<
using ip address on port 11111
* Connection #0 to host 127.0.0.1 left intact
```

結果は、`Host` で指定したポート番号は**無視され**、実際に TCP 通信を行っているポート番号で判定されるようであった。

# まとめ

nginx 1.24.0 のバーチャルホストでは

* `server_name` としてポート番号込みのホストを指定すると、認識されない
* `Host` ヘッダで指定したポート番号と実際の TCP 通信のポート番号が異なる場合は、TCP 通信のポート番号が用いられる

らしいことがわかった。