Nginxリバースプロキシのセットアップ:効率的なトラフィックルーティング
明確なルーティング、正しいヘッダー、WebSocketサポート、タイムアウト、バッファリング、トラブルシューティング手順を含むNginxリバースプロキシをセットアップします。
Nginxリバースプロキシのセットアップ:効率的なトラフィックルーティング
Nginxリバースプロキシのセットアップにより、Nginxが公開Webトラフィックを受信し、1つ以上のバックエンドアプリケーションに転送できるようになります。これは、アプリがNode.js、Python、Go、Java、またはインターネットに直接公開すべきでないその他のサービスで動作している場合に便利です。
ユーザーがアプリのポートに直接接続する代わりに、標準のHTTPまたはHTTPSポートでNginxに接続します。Nginxが公開エッジを処理し、トラフィックを適切な内部サービスに効率的にルーティングします。
リバースプロキシの機能
リバースプロキシはアプリケーションサーバーの前に配置されます。クライアントはNginxと通信し、Nginxはバックエンドと通信します。ブラウザにとっては、Nginxがウェブサイトです。アプリにとっては、元のリクエストの詳細を保持するヘッダーを渡さない限り、Nginxがアップストリームクライアントになります。
このパターンにはいくつかの利点があります:
3000、5000、8080などのプライベートポートでアプリを実行できます。- NginxでTLSを終了できます。
- 異なるホスト名やパスを異なるサービスにルーティングできます。
- バッファリング、タイムアウト、圧縮、キャッシュを追加できます。
- バックエンドの実装詳細を公開ネットワークから隠せます。
127.0.0.1:3000で動作するアプリの基本的なリバースプロキシは次のようになります:
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
proxy_passディレクティブは、Nginxにリクエストの送信先を指示します。proxy_set_header行は、有用なリクエストコンテキストを保持します。これらがないと、アプリはすべてのリクエストがNginxから来たものとしてログに記録し、元のリクエストがHTTPかHTTPSかを認識できない場合があります。
仮想ホスト構造に慣れていない場合は、トラフィックを複数のドメインに分割する前に、Nginxサーバーブロックを確認してください。
ホストまたはパスによるトラフィックルーティング
リバースプロキシルールは通常、ホスト名、パス、またはその両方でルーティングします。ホストベースのルーティングは、別々のアプリが異なるドメインを使用する場合によく見られます:
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://127.0.0.1:4000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
パスベースのルーティングは、1つのドメインが複数のサービスを前面に出す場合に便利です:
server {
listen 80;
server_name example.com;
location /api/ {
proxy_pass http://127.0.0.1:4000/;
}
location / {
proxy_pass http://127.0.0.1:3000;
}
}
proxy_passの末尾のスラッシュに注意してください。Nginxでは、proxy_pass http://backend;とproxy_pass http://backend/;は、locationブロック内で使用された場合に転送されるURIの書き換え方が異なります。アプリが期待する正確なURLパスをテストしてください。
たとえば、/api/usersが予期せずバックエンドに/usersまたは/api/api/usersとして到達した場合は、まずlocationプレフィックスと末尾のスラッシュの組み合わせを確認してください。これは最も一般的なリバースプロキシのミスの1つです。
ヘッダー、タイムアウト、WebSocket
ヘッダーにより、バックエンドが元のリクエストを認識できるようになります。Hostヘッダーは、アプリが絶対URLを構築する場合、許可されたホストを検証する場合、または複数のテナントをサポートする場合に重要です。X-Forwarded-Forは元のクライアントIPを保持するのに役立ちます。X-Forwarded-Protoは、TLS終了後にアプリが安全なリンクを生成するのに役立ちます。
バックエンドがWebSocketを使用する場合は、アップグレードヘッダーを追加します:
location /socket/ {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
タイムアウトはアプリケーションの動作に合わせる必要があります。通常のWebリクエストはすぐに完了するはずです。レポートのエクスポート、ストリーミングエンドポイント、ロングポーリングリクエストにはより多くの時間が必要な場合があります:
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
1つの遅いエンドポイントを隠すために、すべての場所で大きなタイムアウトを設定しないでください。長いタイムアウトはリソースを占有し、実際の障害に気づきにくくする可能性があります。必要な場所だけ調整してください。
バッファリングも重要な設定です。デフォルトでは、Nginxはアップストリームからの応答をクライアントに送信する前にバッファリングできます。これは多くのWebアプリに役立ちますが、ストリーミングエンドポイントではバッファリングを無効にする必要がある場合があります:
proxy_buffering off;
これはストリーミング動作が必要な場合にのみ使用してください。標準のHTMLおよびAPI応答では、バッファリングにより安定性が向上することがよくあります。
TLS終了とHTTPSリダイレクト
多くのセットアップでは、NginxがHTTPSも処理します。これにより、バックエンドアプリはプライベートHTTPポートで実行され、ユーザーはポート443で通常の安全なサイトにアクセスできます。
一般的な形状は次のようになります:
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
リダイレクトサーバーブロックは意図的に小さくなっています。プレーンHTTPトラフィックをHTTPSに移動するという1つの仕事だけを行います。HTTPSサーバーブロックがプロキシを処理します。
アプリがNginxの背後にあり、まだhttp://リンクを生成する場合は、X-Forwarded-Protoを信頼するかどうかを確認してください。多くのフレームワークでは、転送されたヘッダーを使用する前に「trust proxy」設定や許可されたプロキシリストが必要です。アプリケーション層で公開インターネットからの転送ヘッダーを盲目的に信頼しないでください。Nginxのみがアプリポートに到達できるようにしてください。
アップストリームグループとシンプルなロードバランシング
1つのバックエンドでは不十分な場合は、アップストリームグループを定義します:
upstream app_backend {
server 10.0.1.10:3000;
server 10.0.1.11:3000;
keepalive 32;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
オープンソースのNginxはデフォルトでラウンドロビンロードバランシングを使用します。長時間のリクエストにより1つのバックエンドが他のバックエンドよりビジーになる場合は、least_connなどのオプションも使用できます。オープンソースのNginxでのヘルスチェックは主にパッシブです。バックエンドが失敗した場合、Nginxは障害設定に基づいて一定期間利用不可としてマークできます。Nginx Plusにはアクティブヘルスチェックがありますが、すべてのインストールにこれらの機能が存在するとは想定しないでください。
アップストリームブロックのKeepaliveは、バックエンド接続を再利用のために開いたままにします。これは多数の小さなリクエストに役立ちますが、バックエンドはNginxが維持する可能性のあるアイドルおよびアクティブ接続の数を処理できる必要があります。
コンテナとプライベートネットワーク
DockerやKubernetesでは、localhostの意味が変わるため、リバースプロキシのセットアップが混乱することがよくあります。Nginxが1つのコンテナ内で実行されている場合、127.0.0.1:3000はNginxコンテナ自体を指し、別のアプリコンテナを指しません。
Docker Composeでは、サービス名にプロキシします:
location / {
proxy_pass http://app:3000;
}
Kubernetesでは、通常、Service DNS名にプロキシしますが、多くのKubernetesデプロイメントでは手書きのNginxサーバーブロックの代わりにIngressコントローラーを使用します。
簡単なルールは次のとおりです。Nginxが実行されている場所から接続性をテストします。ラップトップからでもバックエンドコンテナからでもありません。これが失敗すると、Nginxも失敗します:
curl -v http://app:3000/
デプロイメントに応じて、Nginxコンテナ内またはNginxホスト上で実行します。
確認すべきセキュリティ境界
リバースプロキシは公開露出を減らすべきであり、誤って増やすべきではありません。バックエンドアプリは通常、プライベートインターフェース、プライベートサブネット、またはコンテナネットワークでリッスンする必要があります。アプリがパブリックVMで0.0.0.0:3000でリッスンしている場合、ユーザーはhttp://example.com:3000にアクセスしてNginxを完全にバイパスできる可能性があります。
ホストのリッスンポートを確認します:
sudo ss -ltnp
バックエンドがコンテナ内のすべてのインターフェースでリッスンする必要がある場合は、ファイアウォールルール、セキュリティグループ、またはコンテナネットワーク設定を使用して、外部からNginxのみがアクセスできるようにします。これは、アプリがTLS、リクエストサイズ制限、レート制限、認証ゲートウェイ、またはIP許可リストをNginxに依存することが多いため重要です。
また、転送ヘッダーにも注意してください。X-Forwarded-Forなどのヘッダーは、Nginxが上書きし、アプリがプロキシのみを信頼しない限り、クライアントが簡単に偽装できます。一般的なNginxパターンは次のとおりです:
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
これにより、クライアントアドレスがチェーンに追加されます。アプリケーションまたはログパイプラインは、どのプロキシアドレスが信頼されているかを認識している必要があります。そうしないと、レート制限や監査ログが間違った「クライアント」IPを記録する可能性があります。
リクエストサイズ制限もこの会話に含まれます。アプリがファイルアップロードを受け入れる場合は、client_max_body_sizeを意図的に設定します:
client_max_body_size 25m;
すべてのルートで必要でない限り、グローバルに巨大な値に上げないでください。プロフィール写真のアップロードエンドポイントとJSONログインエンドポイントは、同じリクエスト本文制限を必要とすべきではありません。
実用的なデプロイメントチェックリスト
リバースプロキシが完了したと呼ぶ前に、ユーザーおよびオペレーターとしてテストします:
curl -I http://example.com/で期待されるリダイレクトまたは応答が表示されること。curl -I https://example.com/で期待されるステータスとヘッダーが表示されること。- アプリログに元のホストと有用なクライアントIPが表示されること。
- WebSocketまたはストリーミングエンドポイントは個別にテストすること。
- 間違ったパス(例:
/api/does-not-exist)はアプリが期待する方法で失敗すること。 - Nginxエラーログは通常のリクエスト中は静かであること。
パスルーティングの場合、各ロケーションに対して3つのURLをテストするのが好きです:ベアプレフィックス、1つの通常のネストされたパス、およびクエリ文字列を含む1つのパス。例:
curl -i http://example.com/api/
curl -i http://example.com/api/users
curl -i 'http://example.com/api/users?page=2'
これらの簡単なチェックで、ユーザーが気付く前に多くの末尾スラッシュのミスをキャッチできます。
リロードするときは、毎回同じ安全なシーケンスを使用します:
sudo nginx -t
sudo systemctl reload nginx
sudo tail -n 50 /var/log/nginx/error.log
アプリがTLS終了の背後にある場合は、生成されたリンク、リダイレクト、Cookie、コールバックURLがHTTPSを使用していることも確認します。ログインフローは、リダイレクトとセキュアCookieがアプリの元のスキームの理解に依存するため、最初に壊れることがよくあります。
一般的な障害パターン
502 Bad Gatewayは通常、Nginxがリバースプロキシの場所に到達したが、アップストリームから有効な応答を取得できなかったことを意味します。バックエンドがダウンしている、ポートが間違っている、アプリが別のインターフェースでリッスンしている、またはファイアウォールによって接続が拒否されている可能性があります。
504 Gateway Timeoutは通常、Nginxが何かに接続したが、時間内に応答を受信しなかったことを意味します。これは、遅いアプリ、ブロックされたデータベースクエリ、過負荷のワーカープール、またはエンドポイントに対して短すぎるタイムアウトが原因である可能性があります。既知の長時間実行エクスポートエンドポイントにはproxy_read_timeoutを増やすことが適切な場合があります。一般的に遅いアプリの修正ではありません。
リダイレクトループは、多くの場合、TLS終了とアプリケーションの信頼設定の不一致から発生します。ブラウザはHTTPSでNginxに到達し、NginxはHTTPでアプリにプロキシし、アプリは元のリクエストがプレーンHTTPであったと認識します。アプリはHTTPSにリダイレクトしますが、同じことが再び発生します。X-Forwarded-Protoを渡すことは修正の半分にすぎません。アプリもプロキシからのそれを信頼する必要があります。
クライアントIPの欠落は、すべてのリクエストが127.0.0.1、Dockerブリッジアドレス、またはプライベートロードバランサーアドレスから来ているように見えることがよくあります。X-Real-IPとX-Forwarded-Forを渡し、アプリケーションとログレイヤーを安全に読み取るように設定します。
パスルーティング後の静的アセットの破損は、多くの場合、アプリが/に存在すると想定していることから発生します。アプリを/admin/の下にマウントすると、それでも/assets/app.cssへのリンクを生成する可能性があります。これはアプリのベースパス設定で修正できる場合があります。Nginxですべてのアセットパスを書き換えようとすることは、通常は脆弱です。
小さな実世界の例
1つのVMで3つのサービスを実行していると想像します:
127.0.0.1:3000のマーケティングサイト127.0.0.1:4000のAPI127.0.0.1:5000の管理ツール
次のようにルーティングできます:
server {
listen 443 ssl;
server_name example.com;
location /api/ {
proxy_pass http://127.0.0.1:4000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /admin/ {
proxy_pass http://127.0.0.1:5000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
これは機能しますが、トレードオフがあります。APIと管理アプリは、どちらもプレフィックスの下で正しく動作する必要があります。そうでない場合は、api.example.comやadmin.example.comなどの別々のホスト名の方がクリーンな場合があります。優れたリバースプロキシ設計は、Nginxが設定を受け入れるようにするだけでなく、アプリケーションが共存できるルーティングを選択することです。
セットアップのテストとトラブルシューティング
リロードする前に必ず設定をテストします:
nginx -t
次にNginxをリロードし、公開ホスト名を通じてリクエストを行います。ブラウザとログの両方を確認します。Nginxアクセスログは、リクエストがNginxに到達したかどうかを示します。エラーログは、接続障害、アップストリームタイムアウト、およびBad Gatewayの詳細を示します。
実用的な例:Node.jsアプリはcurl http://127.0.0.1:3000で正常に動作しますが、公開サイトは502 Bad Gatewayを表示します。これは、Nginxには到達可能ですが、アップストリームと正常に通信できないことを意味します。アプリが期待されるアドレスでリッスンしているか、ポートが正しいか、ローカルファイアウォールが接続をブロックしていないかを確認します。
一般的なリバースプロキシの問題には以下が含まれます:
- 間違ったアップストリームポートまたはアドレス。
- Nginxが別のコンテナで実行されている場合にバックエンドが
localhostにバインドされている。 - WebSocketアップグレードヘッダーの欠落。
Hostヘッダーが予期しないためアプリがリクエストを拒否する。- 末尾のスラッシュによる誤ったURI書き換え。
- 遅いエンドポイントに対して短すぎるタイムアウト。
より深いアップストリーム障害については、Nginx 502トラブルシューティングを参照してください。
助けを求めるタイミング
リバースプロキシが複数のコンテナ、プライベートネットワーク、TLS証明書、またはロードバランスされたアップストリームにまたがる場合は、DevOpsエンジニアに助けを求めてください。これらのセットアップは、Nginxの問題のように見えても、実際にはDNS、ファイアウォール、コンテナネットワーキング、またはアプリケーションのヘルス問題である方法で失敗する可能性があります。
また、管理パネル、内部API、またはステージングサービスを公開リバースプロキシを通じて公開する前に助けを求めるべきです。小さなルーティングのミスが深刻なアクセス問題を引き起こす可能性があります。
Nginxリバースプロキシのセットアップは、Webインフラストラクチャで最も有用なパターンの1つです。ルーティングを明確に保ち、正しいヘッダーを渡し、パスの動作を注意深くテストし、Nginxをバックエンドサービスの安定した公開エントリポイントにしましょう。