nginxのリバースプロキシ設定の基本と実例

※本ページには広告(アフィリエイトプログラム等)が含まれます。

さくらのVPS1台にPHPだけでなくNode.jsやPythonのアプリも同居させたい場面は多い。そんな時に使うのがnginxのリバースプロキシ設定だ。今回は最小構成から実務で必要になるヘッダ・WebSocket対応・複数バックエンドの振り分けまで、実例込みでまとめる。

リバースプロキシとは

クライアントからのリクエストをnginxが受け取り、裏にいるアプリケーションサーバー(Node.jsのExpress、PythonのGunicornなど)に転送して結果を返す構成のこと。1台のVPSでも、80/443番はnginxが一手に引き受け、各アプリは127.0.0.1の別ポートでだけ待受ければいい。

  • TLS終端をnginx側に集約できる(証明書更新もnginx側だけで完結)
  • 静的ファイル(画像・CSS・JS)はnginxが直接返せるので、アプリサーバーの負荷を減らせる
  • ドメイン・パスごとに複数アプリへ振り分けられる(同一VPSで複数サービス運用)

この構成にしておくと、アプリ側の言語やフレームワークが増えても、外から見えるエントリポイントは常にnginxだけで済むのが楽だ。

最小構成

Node.jsアプリが127.0.0.1:3000で動いている前提の最小設定はこうなる。

server {
    listen 80;
    server_name [あなたのドメイン];

    location / {
        proxy_pass http://127.0.0.1:3000;
    }
}

これだけでも動くには動くが、実務でこのまま使うのは避けたい。バックエンド側から見るとリクエスト元IPが全部127.0.0.1になってしまい、アクセスログもRefererもまともに取れないからだ。

必須ヘッダを追加する

proxy_pass だけでは元のリクエスト情報が失われるので、以下の4行は実質必須と考えていい。

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;
}
  • Host $host ... バーチャルホストで動くアプリに正しいホスト名を伝える。無いと localhost や空文字がHostヘッダとして届く
  • X-Real-IP $remote_addr ... 接続元IPをアプリに渡す。アクセス制限やレート制限をアプリ側で実装している場合はこれが無いと全リクエストが同一IP扱いになる
  • X-Forwarded-For $proxy_add_x_forwarded_for ... プロキシを経由した経路のIP一覧。多段プロキシでも辿れる
  • X-Forwarded-Proto $scheme ... httpsでアクセスされたのかhttpなのかをアプリに伝える。無いとアプリが生成するリダイレクトURLがhttpのままになってミックスコンテンツを起こすことがある

特に X-Forwarded-Proto を忘れると、Laravel側で URL::forceScheme('https') していないと生成URLがhttpになる、といった事故につながるので注意。

WebSocket対応

Socket.ioやチャットアプリなど、WebSocketを使うアプリを裏に置く場合はさらに3行追加する。

location /socket.io/ {
    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;
    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はデフォルトでHTTP/1.0でバックエンドに接続するため、proxy_http_version 1.1 を明示しないとUpgradeヘッダが機能せずWebSocketのハンドシェイクが失敗する。ここを忘れて「なぜかWebSocketだけ繋がらない」とハマるケースをよく見る。

upstreamで複数バックエンドを束ねる

アプリを複数プロセスで動かして負荷分散したい場合は upstream ブロックを使う。keepaliveも合わせて設定しておくとバックエンドとの接続を使い回せて効率がいい。

upstream app_backend {
    server 127.0.0.1:3000;
    server 127.0.0.1:3001;
    keepalive 32;
}

server {
    listen 80;
    server_name [あなたのドメイン];

    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;
    }
}

keepalive を使う時は proxy_http_version 1.1;proxy_set_header Connection ""; をセットで書く必要がある点に注意。これを忘れるとkeepaliveが効かず、毎回コネクションを張り直すことになる。

タイムアウトとバッファの調整

バックエンドの処理が重い場合はデフォルトのタイムアウトで切れてしまうことがある。

location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_read_timeout 60s;
    proxy_connect_timeout 5s;
    proxy_buffering on;
}

proxy_read_timeout はバックエンドからの応答待ち時間。集計処理やレポート生成など時間がかかる処理を通すエンドポイントだけ個別に伸ばすのが現実的。proxy_buffering はデフォルトonのままで基本問題ないが、リアルタイム性が求められるストリーミング応答を返すAPIだけ off にすることがある。

PHP-FPMは事情が違う

ここまでの話はNode.jsやPythonのように「HTTPで待ち受けているアプリ」を前提にしたproxy_passの話。PHP-FPMはHTTPサーバーではなくFastCGIプロトコルで待ち受けているので、proxy_passではなくfastcgi_passを使う。

location ~ \.php$ {
    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}

「PHPアプリだからとりあえずproxy_passでいいだろう」と考えて詰まる人が時々いるが、PHP-FPMはHTTPを喋らないのでproxy_passを向けても動かない。Laravelなど純PHPアプリを直接動かす場合はfastcgi_pass、その手前にNode.jsのAPIサーバーなどを別途置く場合だけproxy_pass、と使い分けを覚えておくと混乱しない。

動作確認

設定を反映したら、まずヘッダが正しくバックエンドに渡っているか確認する。

# 構文チェック
sudo nginx -t

# 反映
sudo systemctl reload nginx

# レスポンスヘッダを確認
curl -I https://[あなたのドメイン]/

バックエンド側のアクセスログに実際の接続元IPが記録されているか、X-Forwarded-Protoがhttpsとして渡っているかは、アプリ側で一度ログ出力させて目視確認するのが確実だ。

まとめ

nginxのリバースプロキシはproxy_pass一行だけでも動くが、実務ではX-Forwarded-*系ヘッダとWebSocket対応の3行がほぼ必須になる。PHP-FPMだけはfastcgi_passという別物である点を押さえておけば、1台のVPSで複数言語のアプリを同居させる構成もこれで十分こなせる。

読んで頂いて有り難うございます!