2026年6月14日
2026年6月14日
Nginxのリバースプロキシ設定方法(proxy_passの書き方・転送ヘッダー・動作確認)
Nginxをリバースプロキシにするには、location の中に proxy_pass で転送先を書き、元のホスト名や接続元IPを伝えるヘッダーを proxy_set_header で付け加えます。この記事では、最小構成から始めて、proxy_pass の末尾のスラッシュで変わるURLの扱い、付けておくべきヘッダー、WebSocketやタイムアウトの設定、背後にWordPressを置くときの注意点、502・504が出たときの確認方法までを説明します。
リバースプロキシで何が起きているか
ブラウザが接続するのはNginxだけで、Nginxが裏側のアプリケーション(Node.js、Python、別のWebサーバー上のWordPressなど)にリクエストを中継し、返ってきた応答をブラウザに返します。裏側のサーバーを「バックエンド」や「アップストリーム」と呼びます。
この構成にすると、HTTPSの終端・静的ファイルの配信・アクセス制限をNginxにまとめ、バックエンドは内側のポートだけで待ち受けられます。その代わり、バックエンドから見ると接続してくるのは常にNginxになるため、元の接続元IPやHTTPSかどうかといった情報は、ヘッダーで明示的に渡す必要があります。ここを忘れると、ログのIPがすべて 127.0.0.1 になったり、リダイレクトが http:// に戻ってしまったりします。
最小構成
ローカルの3000番ポートで動くアプリケーションを app.example.com で公開する例です。設定ファイルの場所は、Debian/Ubuntu系なら /etc/nginx/sites-available/(sites-enabled/ にリンク)、RHEL系なら /etc/nginx/conf.d/ が一般的です。
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;
}
}
変更後は、構文を確認してから再読み込みします。nginx -t が失敗した状態で再起動すると、サイト全体が止まることがあるため、順番を守ります。
sudo nginx -t
sudo systemctl reload nginx
curl -I http://app.example.com/
各ヘッダーを付ける理由
proxy_set_header を何も書かないと、Nginxは Host を proxy_pass に書いた宛先(上の例なら 127.0.0.1:3000)に置き換えて送ります。バックエンドがホスト名で処理を分けている場合や、ホスト名を使ってURLを組み立てるアプリケーションでは、これが誤動作の原因になります。
Host $host: ブラウザが要求したホスト名をそのまま渡します。X-Real-IP $remote_addr: Nginxに接続してきたIPアドレスを渡します。X-Forwarded-For $proxy_add_x_forwarded_for: 受け取ったX-Forwarded-Forの末尾に接続元IPを追記して渡します。手前にさらにCDNやロードバランサーがある場合でも経路をたどれます。X-Forwarded-Proto $scheme: ブラウザとの接続がhttpかhttpsかを伝えます。HTTPSをNginxで終端する構成では、これが無いとバックエンドは「HTTPで来た」と判断します。
注意点として、X-Forwarded-For はクライアントが自由に書けるヘッダーです。バックエンドはNginxからの接続に限ってこの値を信用し、外部から直接バックエンドのポートに届かないように、待ち受けアドレスを 127.0.0.1 や内部ネットワークに限定しておきます。
なお、proxy_set_header は、同じ階層に1つでも書くと上の階層(server や http)の指定を引き継がなくなります。location ごとに一部だけ書き足すと、他のヘッダーが消える原因になるので、共通部分は1つのファイルにまとめて include すると管理しやすくなります。
proxy_passの末尾のスラッシュでURLが変わる
proxy_pass の宛先にパス(末尾の / を含む)を書くかどうかで、バックエンドに渡るURLが変わります。ここが一番つまずきやすい点です。
# パスなし: 受け取ったURIをそのまま渡す
location /app/ {
proxy_pass http://127.0.0.1:3000;
# /app/users → http://127.0.0.1:3000/app/users
}
# パスあり: location に一致した部分を置き換える
location /app/ {
proxy_pass http://127.0.0.1:3000/;
# /app/users → http://127.0.0.1:3000/users
}
バックエンドのアプリケーションが /app/ 配下で動く前提で作られているなら前者、ルート(/)で動くものをサブディレクトリで公開したいなら後者を使います。後者の場合、アプリケーションが返すHTMLやリダイレクトの中のURLが /app/ を含まないことがあり、画像やCSSが読み込めなくなります。アプリケーション側に「公開時のベースパス」を設定する項目があれば、それを合わせて設定します。
WebSocketとタイムアウト
チャットや開発サーバーのホットリロードなど、WebSocketを使うアプリケーションでは、接続の切り替え(Upgrade)を中継する設定が別に必要です。map は http の階層(server の外)に書きます。
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
# ...
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_read_timeout 300s;
}
}
proxy_http_version 1.1 は、Upgradeやキープアライブに必要です。長く使われてきた既定値は1.0なので、バージョンによる違いを気にせずに済むよう明示しておきます(2026年10月現在)。
proxy_read_timeout は、バックエンドからの応答を待つ時間です。既定値は60秒で、重い処理や接続を保ち続けるWebSocketではこれを超えると切断されます。値を大きくする前に、本当に時間のかかる処理なのか、バックエンドが止まっているだけなのかを確認します。アップロードを中継する場合は、client_max_body_size(既定値は1MB)も必要な大きさに広げます。
背後にWordPressを置くときの注意点
Nginxの裏で動くWordPressは、X-Forwarded-Proto を自動では解釈しません。HTTPSをNginxで終端していると、WordPressは自分がHTTPで動いていると判断し、管理画面へのログイン後にリダイレクトが繰り返される、画像のURLが http:// になる、といった症状が出ます。wp-config.php の「編集が必要なのはここまで」の行より上に、次のように書きます。
if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && 'https' === $_SERVER['HTTP_X_FORWARDED_PROTO'] ) {
$_SERVER['HTTPS'] = 'on';
}
これは、バックエンドへの接続がNginx経由に限られている場合にだけ安全です。外部から直接バックエンドに届く状態では、誰でもヘッダーを偽ってHTTPS扱いにできてしまいます。設定を変える前に wp-config.php のバックアップを取り、ステージング環境で動作を確かめてから本番に反映します。
また、WordPressのコメントやセキュリティ系プラグインが記録する接続元IPは、何もしないとNginxのIPになります。バックエンドもNginxなら、ngx_http_realip_module の set_real_ip_from(信用するプロキシのアドレス)と real_ip_header X-Forwarded-For を設定すると、ログやPHPに本来の接続元IPが渡ります。
うまく動かないときの確認順
- 502 Bad Gateway: Nginxがバックエンドに接続できていません。エラーログに
connect() failed (111: Connection refused)が出ていれば、バックエンドが起動していないか、ポート番号が違います。curl -I http://127.0.0.1:3000/をサーバー上で実行し、Nginxを通さずに応答するかを確かめます。RHEL系でSELinuxが有効な場合は、(13: Permission denied)と出て接続が拒否されることがあり、setsebool -P httpd_can_network_connect 1で許可します。 - 504 Gateway Timeout: 接続はできたものの、応答が
proxy_read_timeout以内に返っていません。バックエンドのログで処理時間を確認します。 - リダイレクトが繰り返される:
X-Forwarded-Protoが渡っていないか、バックエンドがそれを解釈していません。上のWordPressの設定や、アプリケーションの「プロキシを信用する」設定を確認します。 - CSSや画像だけが404になる:
proxy_passの末尾のスラッシュとベースパスの組み合わせを見直します。
エラーログは /var/log/nginx/error.log が一般的な場所です。sudo tail -f /var/log/nginx/error.log を開いたままブラウザで再読み込みすると、どのリクエストで何が起きたかを対応づけて読めます。
まとめ
proxy_passで転送先を指定し、Host・X-Real-IP・X-Forwarded-For・X-Forwarded-Protoをproxy_set_headerで渡します。proxy_set_headerは同じ階層に1つでも書くと上の階層を引き継がないため、共通部分はincludeでまとめます。proxy_passの宛先にパスを書くと、locationに一致した部分が置き換わります。- WebSocketには
proxy_http_version 1.1とUpgrade用のヘッダーが必要です。 - 背後のWordPressには、HTTPSで受けたことを
wp-config.phpで伝えます。バックエンドは外部から直接届かないようにしておきます。