
フォワードプロキシとリバースプロキシの違い - 仕組み・用途・nginx での設定例
当サイトは Amazon.co.jp を宣伝しリンクすることで紹介料を得る手段を提供する、Amazonアソシエイト・プログラムの参加者です。価格・在庫はリンク先の最新情報をご確認ください。
「プロキシ」という言葉は、社内ネットワークの設定画面にも、nginxの設定ファイルにも、CDNの説明にも出てきます。同じ言葉なのに指しているものがずいぶん違うので、混乱しやすい用語の代表格です。この記事では、フォワードプロキシとリバースプロキシの違いを「誰の代理として動くのか」「ネットワークのどこに置くのか」という2つの軸で整理し、それぞれの用途、ヘッダーの扱い、nginx・Apache・Caddyでの最小設定、よくあるトラブルまでを順に見ていきます。仕様の説明はRFC 9110(HTTP Semantics)とRFC 7239、設定の説明は各ソフトウェアの公式ドキュメントを出典にしています。
プロキシとは「間に立つ中継役」
HTTPの仕様であるRFC 9110は、クライアントとオリジンサーバー(本来のWebサーバー)の間に立つものを仲介者(intermediary)と呼び、代表的な形として次の3つを挙げています。
| 種類 | RFC 9110での位置づけ |
|---|---|
| proxy | クライアントが選んで使う転送役。組織のHTTPリクエストを共通の仲介者に通して、セキュリティやキャッシュに使う |
| gateway(reverse proxy) | 外から見るとオリジンサーバーとして振る舞い、受けたリクエストを内側の別サーバーへ転送する |
| tunnel | メッセージを変更せずに2つの接続を中継するだけの存在 |
RFC 9110では、ふだん「フォワードプロキシ」と呼んでいるものを単に「proxy」、「リバースプロキシ」を「gateway(a.k.a. reverse proxy)」と表現しています。つまり、両者は同じ「中継役」でも、立場がまったく違うということです。
2つの軸で比べる - 誰の代理か、どこに置くか
違いを一言でいえば、フォワードプロキシはクライアントの代理、リバースプロキシはサーバーの代理です。
[フォワードプロキシ] クライアント側に置く。クライアントの代わりに外へ出ていく
社内PC A ─┐
社内PC B ─┼─> フォワードプロキシ ──> インターネット ──> 任意のWebサイト
社内PC C ─┘ (社内ネットワークの出口)
[リバースプロキシ] サーバー側に置く。サーバーの代わりに外からの要求を受ける
任意のクライアント ──> インターネット ──> リバースプロキシ ─┬─> アプリサーバー 1
(サービスの入口) ├─> アプリサーバー 2
└─> API サーバー| 観点 | フォワードプロキシ | リバースプロキシ |
|---|---|---|
| 誰の代理か | クライアント(利用者) | サーバー(サービス提供者) |
| 置く場所 | クライアント側ネットワークの出口 | サーバー側ネットワークの入口 |
| 運用するのは | 社内の情報システム部門、ISPなど | サービスの運営者 |
| クライアントの設定 | 必要(プロキシを明示的に指定する) | 不要(ふつうのWebサーバーに見える) |
| 隠すもの | クライアントの存在(宛先からはプロキシのIPに見える) | バックエンドの構成(クライアントからは1台のサーバーに見える) |
| 宛先 | 不特定多数の外部サイト | 自分たちが管理する特定のバックエンド |
| 代表的な用途 | アクセス制御、ログ取得、キャッシュ、出口の一本化 | TLS終端、負荷分散、キャッシュ、パス振り分け、WAF |
Apache httpdのmod_proxyのドキュメントもこの違いをはっきり書いています。フォワードプロキシは「クライアントが特別に設定されていなければ使えない」中間サーバーであり、リバースプロキシ(ゲートウェイ)は「クライアントからは普通のWebサーバーとまったく同じに見え、クライアント側の特別な設定は不要」なものです。
覚え方としては、クライアントがプロキシの存在を知っているかどうかで考えると迷いません。フォワードプロキシはクライアントが自分で「このプロキシを使う」と指定します。リバースプロキシは、クライアントからするとそれ自体が目的のサーバーです。
フォワードプロキシの仕組みと用途
主な用途
- アクセス制御: 社内から業務に関係のないサイトや危険なサイトへのアクセスを遮断する
- ログ・監査: 誰がどこにアクセスしたかを出口でまとめて記録する
- キャッシュ: 同じリソースへの重複アクセスを手元で返し、回線を節約する
- 出口IPの一本化: 外部サービス側のIP制限(許可リスト)に、プロキシの固定IPだけを登録すればよくなる
- 閉じたネットワークからの外部アクセス: インターネットに直接出られないサーバーから、パッケージの取得などをプロキシ経由で行う
出口IPの一本化は、NATと組み合わせて使われることもあります。IPアドレスやNATの基本はIPアドレス・サブネット・CIDR・NATの入門で解説しています。
HTTPのリクエストはどう変わるか
フォワードプロキシにHTTP(平文)のリクエストを送るとき、クライアントはリクエスト行にパスだけでなく完全なURLを書きます。プロキシはそれを見て、どのサーバーへ転送するかを判断します。
GET http://www.example.com/index.html HTTP/1.1
Host: www.example.comHTTPSはCONNECTメソッドでトンネルを掘る
HTTPSの通信は暗号化されているため、プロキシは中身を読めません。そこで使われるのがCONNECTメソッドです。RFC 9110によると、CONNECTは「リクエストターゲットで示された宛先へのトンネルを確立し、成功したらトンネルが閉じるまで双方向のデータを中身を見ずに転送するだけになる」ことを受け手に求めるメソッドです。
リクエストターゲットはホスト名とポート番号だけの特別な形式で、ポートは省略できません。
CONNECT server.example.com:443 HTTP/1.1
Host: server.example.com:443プロキシが2xxの応答を返すと、その直後からトンネルモードに切り替わります。以降、クライアントはトンネルの中で宛先サーバーと直接TLSのハンドシェイクを行うので、プロキシから見えるのは「どのホストのどのポートにつないだか」までで、URLのパスや本文は見えません。TLSのハンドシェイクの流れはTLS 1.3のハンドシェイクとHTTPSの仕組みを参照してください。
クライアント フォワードプロキシ server.example.com
| CONNECT server.example.com:443 | |
|---------------------------------->| TCP接続 |
| |----------------------------->|
| HTTP/1.1 200 (トンネル確立) | |
|<----------------------------------| |
| TLSハンドシェイク・暗号化された通信(プロキシは中身を見ずに転送) |
|<================================================================>|補足: 企業によっては、プロキシ側で独自の証明書を使ってTLSをいったん復号し、中身を検査する構成(いわゆるTLSインスペクション)をとることがあります。この場合は端末にその組織のルート証明書を入れておく必要があり、CONNECTの単純なトンネルとは別物になります。
クライアント側の設定: HTTP_PROXY / HTTPS_PROXY / NO_PROXY
ブラウザはOSやブラウザの設定画面でプロキシを指定しますが、コマンドラインツールやプログラムでは環境変数で指定するのが慣習です。代表的なのがcurlも対応している次の変数です。
# HTTP 通信に使うプロキシ(curl では小文字のみ有効)
export http_proxy="http://proxy.example.com:8080"
# HTTPS 通信に使うプロキシ
export HTTPS_PROXY="http://proxy.example.com:8080"
# プロキシを経由しない宛先(カンマ区切り)
export NO_PROXY="localhost,127.0.0.1,.example.internal"ここで注意したいのが大文字・小文字の扱いです。curlの公式解説によると、curlは基本的にこれらの変数を大文字でも小文字でも受け付けますが、http_proxyだけは小文字しか受け付けません。理由はCGIの仕組みにあります。Webサーバーはリクエストの Proxy ヘッダーを HTTP_PROXY という環境変数としてCGIスクリプトへ渡すため、大文字を受け付けると、外部のクライアントがヘッダー1つでスクリプト内のcurlのプロキシを書き換えられてしまいます。curlの解説は、大文字版の受け付けが多くのセキュリティ問題の原因になってきたと説明しています。
なお、これらの環境変数は標準規格で定められたものではなく、ツールやライブラリごとに解釈(大文字・小文字の優先順位、NO_PROXYでのワイルドカードやCIDRの扱いなど)が異なります。使うツールのドキュメントを個別に確認するのが安全です。環境変数そのものの扱いは環境変数と.envとシークレット管理の入門でまとめています。
透過プロキシ
クライアントに設定をさせず、ネットワーク機器側で通信を横取りしてプロキシを通す方式を透過プロキシ(transparent proxy)と呼びます。RFC 9110では「interception proxy(一般にtransparent proxyとも呼ばれる)」として説明されており、クライアントが選んだものではない点で通常のプロキシと異なるとされています。公衆無線LANで利用登録前にログインページへ誘導する仕組みや、企業ファイアウォールでの利用ポリシーの強制に使われる、とも書かれています。
あわせてRFC 9110は、こうしたネットワーク層の仲介者はプロトコル上は中間者攻撃(on-path attacker)と区別がつかず、HTTPの意味論に反してセキュリティや相互運用性の問題を起こしがちだ、と注意を促しています。
リバースプロキシの仕組みと用途
リバースプロキシはサービスの入口に立ち、外からのリクエストを受けて内側のサーバーへ渡します。RFC 9110はゲートウェイの用途として、レガシーな仕組みの包み込み、「アクセラレータ」としてのキャッシュによる性能向上、複数台への分割・負荷分散を挙げています。実務でよく使う機能を並べると次のとおりです。
| 機能 | 内容 |
|---|---|
| TLS終端 | 証明書とTLS処理をプロキシに集約し、内側は平文のHTTPでつなぐ |
| 負荷分散 | 複数のバックエンドへリクエストを振り分ける |
| キャッシュ | バックエンドの応答を保存し、同じリクエストにはプロキシから返す |
| 圧縮 | gzipなどで応答を圧縮して転送量を減らす |
| パスやホスト名での振り分け | /api/ はAPIサーバーへ、それ以外はフロントエンドへ、のように宛先を分ける |
| WAF・アクセス制限 | 攻撃パターンの遮断、IP制限、レート制限などを入口でまとめて行う |
| バックエンドの隠蔽 | 内側のサーバーの台数・IP・ソフトウェアを外から見えにくくする |
負荷分散のアルゴリズムはロードバランシングのアルゴリズム入門、入口でのレート制限はレート制限のアルゴリズム入門で詳しく扱っています。
ロードバランサ・APIゲートウェイ・CDNとの関係
リバースプロキシは「役割の名前」であり、製品カテゴリの名前ではありません。そのため、似た言葉との関係を整理しておくと混乱が減ります。
- L7ロードバランサ: HTTPを理解して振り分けるロードバランサは、負荷分散に特化したリバースプロキシといえます。一方、L4ロードバランサはTCP/UDPのレベルで転送するため、HTTPヘッダーを読んだり書き換えたりはしません
- APIゲートウェイ: リバースプロキシに認証・APIキー管理・レート制限・リクエスト変換などAPI向けの機能を足したもの、と捉えると理解しやすいです
- CDN: 世界中の拠点に配置したキャッシュ付きリバースプロキシの集合体です。利用者に近い拠点が応答を返し、オリジンへの到達を減らします。キャッシュの制御はCDNのCache-Controlとs-maxageの実践ガイドを参照してください
実際の構成では、「CDN、クラウドのロードバランサ、nginx、アプリサーバー」のようにリバースプロキシが何段も重なるのが普通です。この多段構成が、次に説明するヘッダーの問題につながります。
プロキシを通すと失われる情報とヘッダー
リバースプロキシを通すと、バックエンドから見たTCP接続の相手はプロキシです。そのままでは、アプリは次の情報を知ることができません。
- 本当のクライアントのIPアドレス
- クライアントが使ったプロトコル(TLS終端していればバックエンドへの通信はHTTPになる)
- クライアントが指定した元のHostヘッダー
これを補うために使われてきたのが X-Forwarded-* 系のヘッダーです。
| ヘッダー | 意味 | 位置づけ |
|---|---|---|
X-Forwarded-For | 経由してきたクライアントやプロキシのIPをカンマ区切りで並べたもの | 事実上の標準(RFCではない) |
X-Forwarded-Proto | クライアントが使ったプロトコル(http / https) | 事実上の標準 |
X-Forwarded-Host | クライアントが送った元のHost | 事実上の標準 |
Forwarded | 上記をまとめて表す標準ヘッダー | RFC 7239で標準化 |
RFC 7239のForwardedヘッダー
RFC 7239は、X-Forwarded-For などの非標準ヘッダーが担ってきた役割を1つのヘッダーにまとめて標準化したものです。パラメーターは4つあります。
for: プロキシにリクエストを送ってきたノード(クライアントや手前のプロキシ)by: リクエストを受けたプロキシ自身のインターフェースhost: プロキシが受け取ったときのHostヘッダーproto: 使われたプロトコル(http / https)
Forwarded: for=192.0.2.60;proto=http;by=203.0.113.43
Forwarded: for=192.0.2.43, for=198.51.100.17
Forwarded: for="[2001:db8:cafe::17]:4711"
Forwarded: for=_hidden1つのプロキシが付けるパラメーターはセミコロンで区切り、複数のプロキシを経由したときはカンマで区切って追記します。IPv6アドレスはコロンを含むため、角かっこで囲んだうえでダブルクォートで囲む必要があります。また、内部のアドレスを伏せたいときは、アンダースコアで始まる「難読化識別子」(_hidden など)を使えます。
ただし、現在も多くのプロキシやフレームワークは X-Forwarded-* 系を主に扱っています。Forwardedに切り替える場合は、経路上のすべての機器とアプリが対応しているかを確認する必要があります。
「そのヘッダーを信じてよいか」がいちばん大事
ここが実務でいちばん事故の多いポイントです。X-Forwarded-For も Forwarded も、ただのリクエストヘッダーです。クライアントは好きな値を書いて送れます。RFC 7239のセキュリティの節も、このヘッダーは経路上のどのノードでも(クライアント自身も)変更できるため完全には信頼できず、既知のプロキシをリスト化しても、信頼できるプロキシに届く前に書かれた値は検証できない、と明記しています。
たとえばnginxでよく使う $proxy_add_x_forwarded_for 変数は、公式ドキュメントによると「クライアントから来た X-Forwarded-For に $remote_addr をカンマ区切りで追記した値」です。クライアントが偽の値を送ってきた場合、その偽の値は先頭に残ります。
クライアントが送ったヘッダー: X-Forwarded-For: 1.2.3.4 ← 偽装された値
nginx が転送するヘッダー: X-Forwarded-For: 1.2.3.4, 198.51.100.7
└ 偽装 └ nginx が実際に見た接続元このため、アプリで「先頭の値をクライアントIPとして使う」実装は危険です。IP制限やレート制限、監査ログに使うなら、次の考え方を守ります。
- 自分が管理するプロキシのIP範囲(信頼するプロキシ)を明示的に決める
- 右端から順に見ていき、信頼するプロキシのIPを取り除いて、最初に現れた信頼できないIPをクライアントIPとみなす
- 外部から直接届いた
X-Forwarded-*は、入口のプロキシで上書きまたは削除する
nginxではngx_http_realip_moduleの set_real_ip_from(信頼するアドレスの指定)、real_ip_header(読むヘッダー。既定は X-Real-IP)、real_ip_recursive を使って、この判定をnginx側で行えます。このモジュールは既定ではビルドされず、--with-http_realip_module を付けてビルドする必要があると公式ドキュメントにあります(ディストリビューションのパッケージに含まれているかは、nginx -V の出力で確認してください)。Caddyのreverse_proxyは、なりすましを防ぐため既定ではクライアントから届いた X-Forwarded-* を無視し、信頼するプロキシのCIDRを trusted_proxies で設定したときだけ採用する、と公式ドキュメントに書かれています。
サーバー別の最小設定例
ここからは、リバースプロキシの最小設定を3つのソフトウェアで比べます。いずれも app.example.com へのアクセスを、同じマシンの8080番ポートで動くアプリへ転送する例です。
nginx: proxy_pass
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://127.0.0.1:8080;
# 元の Host を渡す(既定では $proxy_host = "127.0.0.1:8080" になる)
proxy_set_header Host $host;
# クライアント IP とプロトコルを伝える
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_set_header | Host $proxy_host と Connection close | 元のHostとConnectionは、そのままでは転送されない |
proxy_http_version | 1.1(1.29.7以降。それより前は 1.0) | バックエンドとの通信に使うHTTPのバージョン |
proxy_connect_timeout | 60s | バックエンドとの接続確立の待ち時間(通常75秒を超えられない) |
proxy_read_timeout | 60s | 応答の読み取りで、連続する2回の読み取りの間の待ち時間 |
proxy_buffering | on | バックエンドの応答をいったんバッファにためてから返す |
注意点として、proxy_set_header は「現在のレベルに proxy_set_header が1つもない場合に限って」上位のレベル(http や server)から継承されます。location に1行でも proxy_set_header を書くと、server に書いたものは引き継がれないので、必要なヘッダーはまとめて書き直す必要があります。
キャッシュを使いたい場合は、http コンテキストで proxy_cache_path を定義し、location などで proxy_cache を指定します。
http {
proxy_cache_path /data/nginx/cache levels=1:2 keys_zone=one:10m;
server {
location / {
proxy_pass http://127.0.0.1:8080;
proxy_cache one;
}
}
}Apache httpd: ProxyPass
# mod_proxy と mod_proxy_http を有効にしておく
<VirtualHost *:80>
ServerName app.example.com
ProxyPreserveHost On
ProxyPass "/" "http://127.0.0.1:8080/"
ProxyPassReverse "/" "http://127.0.0.1:8080/"
</VirtualHost>Apacheのポイントは次のとおりです。
- リバースプロキシに
ProxyRequestsは不要です。ProxyRequests Onはフォワードプロキシを有効にする設定で、公式ドキュメントは、サーバーを保護する前に有効にしてはいけない、オープンプロキシはネットワークにもインターネット全体にも危険だ、と警告しています ProxyPassReverseは、バックエンドが返すリダイレクトのLocationなどのヘッダーをプロキシ側のURLへ書き換えます。これがないと、リダイレクトでクライアントがバックエンドのURLへ直接飛ばされてしまいますProxyPreserveHostは既定でOffです。Onにすると、元のHostヘッダーをそのままバックエンドへ渡します- リバースプロキシとして動くとき、mod_proxy_httpは既定(
ProxyAddHeaders On)でX-Forwarded-For・X-Forwarded-Host・X-Forwarded-Serverを付けます - フォワードプロキシでHTTPSを扱うには、CONNECTを処理するmod_proxy_connectが必要です
Caddy: reverse_proxy
app.example.com {
reverse_proxy localhost:8080
}Caddyは設定がもっとも短く済みます。公式ドキュメントによると、X-Forwarded-For を追加・更新し、X-Forwarded-Proto と X-Forwarded-Host を設定したうえで、Hostを含むそのほかのヘッダーは変更せずにバックエンドへ渡します。サイトアドレスにドメイン名を書けば、証明書の取得とHTTPSも自動で行われます。
複数のバックエンドを並べると負荷分散になり、既定の振り分け方式(lb_policy)は random です。
app.example.com {
reverse_proxy node1:80 node2:80 node3:80
}よくあるトラブルと対処
502 Bad Gatewayと504 Gateway Timeout
どちらも「プロキシやゲートウェイとして動いているサーバー」が返すステータスコードで、問題はたいていプロキシの向こう側にあります。RFC 9110の定義は次のとおりです。
- 502 Bad Gateway: 内側のサーバーにアクセスした際に、無効な応答を受け取った
- 504 Gateway Timeout: 必要な上流サーバーから、時間内に応答を受け取れなかった
実務では、502はバックエンドのプロセスが落ちている、ポートやソケットのパスが違う、バックエンドが接続を途中で切った、といった場面で、504はバックエンドの処理が重く proxy_read_timeout などの待ち時間を超えた場面でよく見かけます。まずプロキシのエラーログで、接続できなかったのか、待ち時間を超えたのかを確認するのが近道です。ステータスコード全般はHTTPステータスコード入門にまとめています。
Hostヘッダーが変わってしまう
nginxは既定で Host を $proxy_host(proxy_pass に書いたホスト名とポート)に置き換えます。そのため、バックエンドのアプリが生成するURLが http://127.0.0.1:8080/... になったり、Hostでバーチャルホストを切り替えているバックエンドで意図しないサイトが返ったりします。元のHostを渡したいなら proxy_set_header Host $host; を書きます。Apacheなら ProxyPreserveHost On です。
逆に、バックエンドが外部のHTTPSサービスで、そのサービス自身のホスト名でアクセスしなければならない場合は、元のHostを渡すと失敗します。Caddyはv2.11.0以降、HTTPSのバックエンドへ転送するときにHostを自動で設定するようになった、と公式ドキュメントに書かれています。
proxy_passの末尾スラッシュで転送先のパスが変わる
nginxの proxy_pass は、URIを含むかどうかで挙動が変わります。ここでの「URI」には末尾の / だけでも含まれる点が落とし穴です。
# (1) URI なし: クライアントのリクエスト URI がそのまま転送される
location /api/ {
proxy_pass http://127.0.0.1:8080;
}
# /api/users -> http://127.0.0.1:8080/api/users
# (2) URI あり(末尾の / だけでも URI 扱い): location に一致した部分が置き換わる
location /api/ {
proxy_pass http://127.0.0.1:8080/;
}
# /api/users -> http://127.0.0.1:8080/users公式ドキュメントは、URIを指定した場合は「正規化したリクエストURIのうちlocationに一致した部分が、ディレクティブで指定したURIに置き換えられる」と説明しています。/api/ を外して転送したいなら(2)、そのまま渡したいなら(1)を選びます。
Apacheの ProxyPass にも似た注意があり、公式ドキュメントは「1つ目の引数が / で終わるなら2つ目も / で終わらせる(逆も同様)。そうしないとバックエンドへのリクエストで必要なスラッシュが欠けることがある」と書いています。
WebSocketがつながらない
WebSocketは、HTTP/1.1の Upgrade ヘッダーでプロトコルを切り替えて始まります。nginxの公式ドキュメントによると、Upgrade と Connection はホップバイホップ(隣の相手との間でだけ意味を持つ)ヘッダーなので、プロキシはそのままでは転送しません。明示的に渡す設定が必要です。
http {
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
location /chat/ {
proxy_pass http://backend;
# proxy_http_version 1.1; # 1.29.7 より前のバージョンでは必須
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}
}もう1つの落とし穴が待ち時間です。既定では、バックエンドが60秒間データを送らないと接続が閉じられます。proxy_read_timeout を延ばすか、サーバー側から定期的にpingフレームを送るのが対処法です。Apacheは2.4.47以降、ProxyPass に upgrade=websocket を付けるとmod_proxy_httpがWebSocketを扱えます(それより前はmod_proxy_wstunnelを使います)。Caddyは追加設定なしでアップグレードを処理します。WebSocketそのものの仕組みはWebSocket・SSE・ポーリングの比較を参照してください。
アクセスログのクライアントIPが全部プロキシのIPになる
リバースプロキシの裏にあるサーバーでは、TCPの接続元は常にプロキシです。そのため、何もしないとアプリのログやIP制限には、プロキシ(あるいはロードバランサ)のIPしか出てきません。対処の流れは次のとおりです。
- 入口のプロキシで
X-Forwarded-For(またはForwarded)を付ける - 内側のサーバーやアプリで「信頼するプロキシのIP範囲」を設定する
- その範囲から来た接続に限って、ヘッダーからクライアントIPを取り出す
nginxならrealipモジュール、Caddyなら trusted_proxies、各Webフレームワークにも同様の「信頼するプロキシ」設定があります。手順2を省いて誰から来たヘッダーでも信じてしまうと、前述のとおりIPを簡単に偽装されます。
リダイレクト先がhttpになる・ループする
TLSをリバースプロキシで終端すると、バックエンドには平文のHTTPで届きます。アプリが「HTTPでアクセスされた」と判断してHTTPSへのリダイレクトを返し、それがまたプロキシ経由でHTTPとして届く、というループや、http:// のURLを生成してしまう問題が起きます。X-Forwarded-Proto を付け、アプリ側でそれを信頼する設定(信頼するプロキシの指定とセット)を入れると解消します。
どちらを使うべきか - 選び方の整理
フォワードプロキシとリバースプロキシは、どちらかを選ぶものではなく、解決したい課題の側で決まるものです。
| やりたいこと | 使うもの |
|---|---|
| 社内からのWebアクセスを制御・記録したい | フォワードプロキシ |
| インターネットに出られないサーバーから外部のパッケージを取りたい | フォワードプロキシ |
| 外部サービスのIP許可リストに出口IPを1つだけ登録したい | フォワードプロキシ(またはNATゲートウェイ) |
| 1つのドメインで複数のアプリを公開したい | リバースプロキシ(パスやホスト名で振り分け) |
| 証明書の管理を1か所にまとめたい | リバースプロキシ(TLS終端) |
| アプリを複数台に増やしたい | リバースプロキシ / ロードバランサ |
| 静的ファイルや重いページを速く返したい | リバースプロキシのキャッシュ / CDN |
ソフトウェアの選び方としては、リバースプロキシならnginx・Apache httpd・Caddyのいずれでも基本的なことはできます。設定の短さと証明書の自動化を重視するならCaddy、既存の資産や情報量を重視するならnginxやApache、という選び方が一般的です。フォワードプロキシ専用に作られたソフトウェアとしてはSquidなどがあります。
まとめ
- フォワードプロキシはクライアントの代理として出口に置き、リバースプロキシはサーバーの代理として入口に置く
- クライアントがプロキシの存在を知っていて自分で設定するのがフォワード、普通のWebサーバーに見えるのがリバース
- HTTPSのフォワードプロキシはCONNECTメソッドでトンネルを作り、プロキシは通信の中身を見ない
- 環境変数のhttp_proxyは、curlでは安全上の理由で小文字だけが有効
- リバースプロキシの裏ではクライアントIP・プロトコル・Hostが失われるので、
X-Forwarded-*やForwarded(RFC 7239)で補う。ただし、信頼するプロキシを決めずにヘッダーを信じてはいけない - nginxでは、Hostの既定値、
proxy_passの末尾スラッシュ、WebSocketのUpgradeヘッダー、60秒の待ち時間が典型的なつまずきどころ
用語としては紛らわしい2つですが、「誰の代理か」と「どこに置くか」を押さえておけば、設定画面やドキュメントでどちらの話をしているのかはすぐに見分けられるようになります。
参考資料
- RFC 9110: HTTP Semantics(3.7 Intermediaries、9.3.6 CONNECT、15.6 Server Error)
- RFC 7239: Forwarded HTTP Extension
- Module ngx_http_proxy_module(nginx公式)
- WebSocket proxying(nginx公式)
- Module ngx_http_realip_module(nginx公式)
- mod_proxy(Apache HTTP Server 2.4 公式)
- reverse_proxy(Caddy公式)
- Proxy environment variables(Everything curl)
- Proxy servers and tunneling(MDN)
- X-Forwarded-For(MDN)
- Forwarded(MDN)


