Nginxリバースプロキシ入門 — 1台のVPSで複数サービスを公開する【2026年】
本記事はアフィリエイト広告(A8.net)を含みます。価格・仕様は2026年6月時点の目安です。最新は各公式サイトでご確認ください。
TL;DR
1台のVPSでアプリを複数動かすと、それぞれ別のポート(localhost:3000、localhost:8080 など)で待ち受けます。ですが外部に公開できる入口は基本的に80番(HTTP)と443番(HTTPS)の2つだけです。この差を埋めるのがリバースプロキシです。
Nginxを入口に置き、ドメイン名(Host ヘッダ)で振り分けて各アプリへ転送します。SSL終端(HTTPS化)もNginx側でまとめれば、後ろのアプリは平文のHTTPで動かしたままで済みます。証明書はLet’s Encryptで無料・自動更新できます。
設定がシンプルで自動HTTPSまで欲しい場合は、Caddyという軽量な選択肢もあります。本記事ではNginxを軸に基本を押さえ、最後にCaddyと比較します。
リバースプロキシとは何か / 何が嬉しいか
「リバースプロキシ」は、クライアントからのリクエストを受け取り、内部のサーバーへ代理で転送するサーバーです。利用者から見える顔はNginx1つだけで、その裏に複数のアプリが隠れている構成になります。
1台のVPSで複数サービスを公開する場面で、これがないと困ることを並べます。
- ポートの問題: アプリは
:3000や:8080で動きますが、利用者にhttps://example.com:8080を踏ませるのは現実的ではありません。443番に集約したいです。 - ドメイン振り分け:
app.example.comはNode.jsアプリ、blog.example.comはWordPressへ、と1つのIPで複数ドメインを捌きたいです。 - HTTPSの一元化: 証明書を各アプリに配るのは面倒です。Nginxで終端すれば1か所で管理できます。
- アプリを隠せる: 後ろのアプリは
127.0.0.1(ループバック)だけで待ち受け、外部に直接さらさずに済みます。
つまりNginxが「玄関」となり、ドメイン・ポート・証明書・公開範囲をまとめて引き受ける、というのがリバースプロキシの役割です。
ポートとドメインの対応をどう捌くか
外部からの入口は80番(HTTP)と443番(HTTPS)です。1つのIPアドレスに複数のドメインを向けておき、Nginxが受け取ったHost ヘッダ(要求されたドメイン名)を見て転送先を決めます。これが名前ベースのバーチャルホストです。
| 公開ドメイン | 内部の待ち受け | 用途の例 |
|---|---|---|
app.example.com | 127.0.0.1:3000 | Node.js / SPA APIサーバー |
blog.example.com | 127.0.0.1:8080 | WordPress(PHP-FPM等) |
grafana.example.com | 127.0.0.1:3001 | 監視ダッシュボード |
前提として、各ドメインのDNS(Aレコード/AAAAレコード)をこのVPSのIPに向けておきます。Nginxは届いたリクエストの宛先ドメインで server ブロックを選び、対応する内部ポートへ渡します。
基本の server / location / proxy_pass
まず最小構成です。1つのアプリ(127.0.0.1:3000 で動いているとします)をHTTPで公開する例です。Ubuntu系なら /etc/nginx/sites-available/app.conf に置き、sites-enabled へシンボリックリンクを張る運用が一般的です。
server {
listen 80;
listen [::]:80;
server_name app.example.com;
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;
}
}
各ディレクティブの意味です。
server_name: このブロックが担当するドメイン名です。リクエストのHostがここに一致したブロックが使われます。location /: パスごとの処理単位です。/は全パスにマッチします。proxy_pass: 転送先です。http://127.0.0.1:3000のように内部アドレスを書きます。proxy_set_header: 転送時にヘッダを付け替えます。Hostを渡さないと後ろのアプリが自分のドメインを判別できません。X-Real-IP/X-Forwarded-Forで本来のクライアントIPを、X-Forwarded-Protoで元がHTTPかHTTPSかを後ろへ伝えます。これらを省くと、アプリ側でログのIPがすべてNginxのIPになったり、HTTPS判定を誤ったりします。
設定を変えたら、文法チェックしてから反映します。
sudo nginx -t # 設定ファイルの文法チェック
sudo systemctl reload nginx # 無停止でリロード
複数サービスのバーチャルホスト
ドメインごとに server ブロックを足していくだけで複数サービスを公開できます。ファイルを分けて1ドメイン1ファイルにすると管理が楽です。
app.example.com(Node.jsアプリ)の例です。
# /etc/nginx/sites-available/app.conf
server {
listen 80;
server_name app.example.com;
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;
}
}
blog.example.com(別ポートのアプリ)の例です。
# /etc/nginx/sites-available/blog.conf
server {
listen 80;
server_name blog.example.com;
location / {
proxy_pass http://127.0.0.1:8080;
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 -t → reload です。
sudo ln -s /etc/nginx/sites-available/app.conf /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/blog.conf /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
ここでよくあるのが、後ろのアプリを 0.0.0.0(全インターフェース)で待ち受けてしまい、http://VPSのIP:3000 でNginxを迂回して直接アクセスできてしまう事故です。アプリ側は 127.0.0.1:3000 のようにループバックだけで待ち受けるか、ファイアウォール(ufw等)で3000・8080番を外部から閉じておきます。サーバーの初期防御はVPS初期セキュリティ設定チェックリストも参照してください。
SSL終端(Let’s Encrypt連携)
HTTPS化はNginx側でまとめて行います(SSL終端)。後ろのアプリは平文HTTPのまま、利用者との間だけ暗号化される形です。証明書はLet’s EncryptをCertbotで取得・自動更新します。
Certbotを入れて、Nginx設定を見ながら証明書を発行します。
# Ubuntu系の例
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d app.example.com -d blog.example.com
--nginx プラグインを使うと、Certbotが各 server ブロックを自動で443番対応に書き換え、証明書のパスや80→443リダイレクトも追記します。書き換え後はおおむね次の形になります。
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name app.example.com;
ssl_certificate /etc/letsencrypt/live/app.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;
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;
}
}
# 80番に来たアクセスは443へリダイレクト
server {
listen 80;
listen [::]:80;
server_name app.example.com;
return 301 https://$host$request_uri;
}
Let’s Encryptの証明書は有効期間90日です。Certbotはインストール時に更新タイマー(systemd timerまたはcron)を仕込むため、通常は自動更新されます。動作確認はドライランで行います。
sudo certbot renew --dry-run
ここで先ほどの X-Forwarded-Proto $scheme が効いてきます。利用者→Nginx間はHTTPS、Nginx→アプリ間はHTTPなので、アプリは自分への接続を「HTTP」と誤認しがちです。X-Forwarded-Proto を見るようアプリ側を設定すれば、リダイレクトループやCookieのSecure属性の不整合を避けられます。
WebSocket / ヘッダ転送の注意
WebSocket(チャット、リアルタイム更新、開発用のホットリロード等)を使うアプリでは、追加の設定が要ります。WebSocketはHTTPの Upgrade ヘッダで通常接続から切り替わるためです。location に以下を足します。
location / {
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;
}
ポイントです。
proxy_http_version 1.1: WebSocketのアップグレードにはHTTP/1.1が必要です。Nginxはデフォルトで後ろへ1.0を使うため明示します。Upgrade/Connectionヘッダ: これを転送しないと接続が普通のHTTPのまま切り替わらず、WebSocketが確立できません。- 長時間接続のタイムアウト: WebSocketは接続を張りっぱなしにします。無通信で切れる場合は
proxy_read_timeout 3600s;のように読み取りタイムアウトを延ばします。
なお、転送するアップロードのサイズが大きいアプリでは client_max_body_size(デフォルトは1MB)も見直します。ファイルアップロードが413エラーで弾かれる典型原因です。
# server または location ブロック内に記述する。
# (http ブロック全体に効かせたい場合は /etc/nginx/nginx.conf の
# 既存の http {} 内に置く。設定断片で http {} を新たに書くと二重定義になり
# nginx -t が失敗するので注意)
client_max_body_size 20m; # 例: 20MBまで許可
Caddyという軽量な選択肢(自動HTTPS)
Nginxの代わりにCaddyを入口に置く選択肢もあります。最大の特徴は自動HTTPSで、設定にドメインを書くだけで、Caddyが起動時にLet’s Encrypt(またはZeroSSL)から証明書を取得し、更新まで面倒を見ます。Certbotを別途仕込む必要がありません。
同じ「2サービスをドメインで振り分け、HTTPS化する」構成を Caddyfile で書くと、これだけで済みます。
app.example.com {
reverse_proxy 127.0.0.1:3000
}
blog.example.com {
reverse_proxy 127.0.0.1:8080
}
reverse_proxy は X-Forwarded-For などのヘッダ付与やWebSocketのアップグレード転送をデフォルトで行います。Nginxで手書きしていた proxy_set_header 群が省けるのが利点です。
NginxとCaddyの比較です。
| 観点 | Nginx | Caddy |
|---|---|---|
| HTTPS | Certbot等で設定(自動更新は別途タイマー) | 設定にドメインを書くだけで自動取得・更新 |
| 設定の量 | ヘッダ等を明示的に書く(細かく制御可能) | 既定が親切で短く書ける |
| 設定の細かさ | 非常に細かく制御できる | 既定で十分だが、超細かい調整はNginxに分がある |
| 性能・実績 | 高負荷・大規模での実績が豊富 | 中小規模で十分、近年実績も増加 |
| 用途の目安 | 細かいチューニング・既存資産がある場合 | 個人〜小規模で手早く立てたい場合 |
「とにかく早く複数サービスをHTTPSで公開したい」「証明書管理を意識したくない」ならCaddyが楽です。一方、細かいキャッシュ制御・rewrite・既存のNginx資産がある、あるいは将来的に大規模化を見据えるならNginxが順当です。どちらもリバースプロキシとしての考え方(ドメインで振り分け→内部ポートへ転送→SSL終端)は同じです。
実践: どのVPSで動かすか
リバースプロキシ構成は1台のVPSに複数サービスを同居させるため、メモリの余裕が効きます。Nginx自体は軽量ですが、後ろのアプリ(Node.js、PHP-FPM、データベース等)が積み上がるとメモリを食います。国内で実績のあるVPSを2つ挙げます。
- ConoHa VPS: 時間課金に対応し、検証で立てて消す使い方がしやすいです。管理画面が分かりやすく、初めての自己ホストでも扱いやすい構成です。
- Xserver VPS: レンタルサーバーで知られる事業者のVPSです。料金と性能のバランスが取りやすく、長期で1台を常駐させる用途に向きます。
どちらもUbuntu等のLinuxイメージにNginxを入れて上記の構成を組めます。まずは小さいプランで1〜2サービスから始め、メモリ使用率を見ながらプランを上げるのが安全です。VPSの選び方の詳細は個人開発者向けVPS比較も参考にしてください。
まとめ
| 項目 | 要点 |
|---|---|
| リバースプロキシの役割 | 80/443の入口を1つにまとめ、ドメインで内部ポートへ振り分ける |
| 振り分けの基準 | server_name と Host ヘッダの一致(名前ベースのバーチャルホスト) |
| 必須ヘッダ | Host / X-Real-IP / X-Forwarded-For / X-Forwarded-Proto を転送する |
| SSL終端 | Nginxで443を終端、後ろはHTTP。Let’s Encrypt + Certbotで無料・自動更新 |
| WebSocket | proxy_http_version 1.1 + Upgrade / Connection ヘッダの転送が必要 |
| 後ろのアプリの公開範囲 | 127.0.0.1 で待ち受け+ファイアウォールで直アクセスを塞ぐ |
| 軽量な代替 | Caddyは設定にドメインを書くだけで自動HTTPS。手早く立てたい場合に有利 |
1台のVPSで複数サービスを公開する構成は、「Nginxを玄関に、後ろのアプリはループバックで隠す」という形に収束します。まず1サービスをHTTPで通し、Certbotで443化し、必要ならWebSocketヘッダを足す——この順で積めば迷いません。