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が渡ります。

うまく動かないときの確認順

  1. 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 で許可します。
  2. 504 Gateway Timeout: 接続はできたものの、応答が proxy_read_timeout 以内に返っていません。バックエンドのログで処理時間を確認します。
  3. リダイレクトが繰り返される: X-Forwarded-Proto が渡っていないか、バックエンドがそれを解釈していません。上のWordPressの設定や、アプリケーションの「プロキシを信用する」設定を確認します。
  4. 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 で伝えます。バックエンドは外部から直接届かないようにしておきます。

お気軽にご相談ください

お見積りへ お問い合わせへ
▲