信頼済みプロキシ
OxPHP をリバースプロキシ(Kubernetes Ingress、Cloudflare、AWS ALB、nginx)の背後で動作させると、すべてのリクエストはプロキシの IP アドレスから届きます。信頼済みプロキシの設定がないと、レート制限、アクセスログ、そして $_SERVER['REMOTE_ADDR'] のいずれもが、実際のクライアントではなくプロキシの IP を参照してしまいます。
設定
# Comma-separated CIDR list
TRUSTED_PROXIES="10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"
# Shorthand: all RFC-1918 + loopback + link-local (IPv4 and IPv6)
TRUSTED_PROXIES="private"
# Keywords and CIDRs combine in one list
TRUSTED_PROXIES="private,cloudflare,100.64.0.0/10"カンマ区切りの各エントリーは、CIDR、単独の IP アドレス(1 台のホスト)、またはキーワード private と cloudflare のいずれかで、大文字・小文字は問いません。そのいずれでもないエントリーは起動時エラーになります。
未設定の場合、OxPHP はすべての転送ヘッダーを無視します。これが安全なデフォルトです。
仕組み
信頼済みの IP からリクエストが届くと、OxPHP は次の優先順位で転送ヘッダーを検査します。
Forwarded(RFC 7239) — 標準化されたヘッダーX-Forwarded-For/X-Forwarded-Proto/X-Forwarded-Host/X-Forwarded-Port— 事実上のフォールバック
Forwarded ヘッダーが存在する場合、X-Forwarded-* ヘッダーは無視されます。
クライアント IP の抽出
OxPHP は**最右端の非信頼(rightmost-non-trusted)**アルゴリズムを使用します。これは nginx(real_ip_recursive on)、Caddy、Traefik、Apache が採用しているのと同じ方式です。
X-Forwarded-For: 203.0.113.50, 172.16.1.1, 10.0.0.5
TCP peer: 10.0.0.1 (trusted)
Walk right-to-left:
10.0.0.5 → trusted → skip
172.16.1.1 → trusted → skip
203.0.113.50 → NOT trusted → client IPこれにより、先頭に値を付け加えることによるなりすましを防止できます。攻撃者は左側に偽の IP を追加できますが、最右端の非信頼 IP はチェーン内の最後の信頼済みプロキシによって設定されたものです。
エントリーはポートを持つことがあります。203.0.113.50:4711 や [2001:db8::1]:4711 のような形式で、一部のロードバランサー(Azure Application Gateway など)が X-Forwarded-For に書き込むものです。クライアントアドレスがそのようなエントリーから取得された場合、そのポートが REMOTE_PORT になります。
走査は、アドレスを持たないエントリーや IP アドレスとして解析できないエントリー(空の要素、unknown、難読化された RFC 7239 識別子)、そして OxPHP がテキストとして読めないヘッダー行(たとえば非 ASCII バイトを含む行)で停止します。チェーンのその部分はクライアントが送ったものにすぎない可能性があるため、その地点より左側は一切使用されません。クライアントアドレスは、その地点より右側で最も右にある非信頼エントリーです。そこにあるエントリーがすべて信頼済みの場合はそのうち最も左のものになり、そこにエントリーがまったくない場合はアドレスは書き換えられず、ポートも含めて接続元のピアのままになります。
X-Forwarded-For: 198.51.100.9, unknown, 10.0.0.5
TCP peer: 10.0.0.1 (trusted)
Walk right-to-left:
10.0.0.5 → trusted → skip
unknown → does not parse → stop
198.51.100.9 → never reached
Client IP: 10.0.0.5 (every entry right of the stop is trusted → the leftmost of them)cloudflare キーワードを指定すると、転送チェーンから Cloudflare のエッジ経由で届いたことがわかるリクエストは、代わりに CF-Connecting-IP ヘッダーからクライアントアドレスを取得します。CDN とプラットフォームのロードバランサーの背後を参照してください。
変わる内容
TRUSTED_PROXIES が設定されていて、接続元の IP が信頼済みの場合は次のようになります。
| コンポーネント | 信頼済みプロキシなし | 信頼済みプロキシあり |
|---|---|---|
$_SERVER['REMOTE_ADDR'] |
プロキシ IP | 実際のクライアント IP。転送ヘッダーからアドレスが得られない場合はプロキシ IP |
$_SERVER['REMOTE_PORT'] |
プロキシの送信元ポート | Forwarded または X-Forwarded-For の ip:port エントリーのクライアントポート、なければ 0。アドレスが書き換えられない場合はピアのポート |
$_SERVER['HTTPS'] |
OxPHP の TLS 設定に基づく | Forwarded: proto= または X-Forwarded-Proto から |
$_SERVER['REQUEST_SCHEME'] |
TLS に基づく http または https |
転送されたプロトコルから |
$_SERVER['SERVER_NAME'] |
Host ヘッダーから |
Forwarded: host= または X-Forwarded-Host から |
$_SERVER['SERVER_PORT'] |
Host ヘッダーから |
X-Forwarded-Port から、なければ X-Forwarded-Host / Forwarded: host= のポート、それもなければ 443/80 |
| レート制限 | プロキシ IP 単位 | クライアント IP 単位 |
| アクセスログ | プロキシ IP | 実際のクライアント IP |
クライアントアドレスが書き換えられる場合、アドレスの取得元のエントリーがポートを持っていれば(RFC 7239 の Forwarded: for="ip:port" ノード、または X-Forwarded-For の ip:port エントリー)、REMOTE_PORT はクライアントの送信元ポートになり、それ以外の場合は 0 になります。ポートは復元できないため、推測する代わりにゼロが設定されます。CF-Connecting-IP から取得したアドレスもポートを持たないため、その場合も REMOTE_PORT は 0 になります。クライアント IP の抽出で説明したようにヘッダーからクライアントアドレスが得られず、アドレスが書き換えられない場合、REMOTE_PORT は接続元のピアのポートのままです。
private ネットワーク
private の短縮指定には次のものが含まれます。
| ネットワーク | 説明 |
|---|---|
10.0.0.0/8 |
クラス A プライベート |
172.16.0.0/12 |
クラス B プライベート |
192.168.0.0/16 |
クラス C プライベート |
127.0.0.0/8 |
ループバック |
169.254.0.0/16 |
リンクローカル |
::1/128 |
IPv6 ループバック |
fc00::/7 |
IPv6 ユニークローカル |
fe80::/10 |
IPv6 リンクローカル |
共有アドレス空間(100.64.0.0/10、RFC 6598)は private に含まれません。ロードバランサーがこの範囲から接続してくる場合は、キーワードと並べて列挙してください(TRUSTED_PROXIES="private,100.64.0.0/10")。
cloudflare ネットワーク
cloudflare キーワードは、Cloudflare が cloudflare.com/ips で公開しているエッジの範囲を追加します。2026-10-05 時点で公開されている IPv4 の 15 範囲と IPv6 の 7 範囲です。このリストは OxPHP にコンパイル時に組み込まれており、OxPHP のリリースでのみ変わります。Cloudflare のリストのほうが先に変わった場合は次のようにします。
- 新しい範囲。 キーワードと並べて列挙してください(
TRUSTED_PROXIES="private,cloudflare,<new range>")。列挙した範囲は Cloudflare のエッジではなく自分のプロキシの 1 つとして扱われるため、リリースでその範囲がキーワードに追加されるまで、そこを経由するリクエストはCF-Connecting-IPからではなく、最右端の非信頼ルールによってアドレスを取得します。 - 撤回された範囲。 キーワードから差し引くことはできません。OxPHP のリリースより先に除外するには、キーワードを使う代わりに、引き続き信頼する範囲を CIDR として列挙してください。その場合、キーワードに結び付いている
CF-Connecting-IPの読み取りも行われなくなります。
CDN とプラットフォームのロードバランサーの背後
よくあるデプロイ構成では、ホスティングプラットフォームのロードバランサーの前段に CDN を置きます。
client → Cloudflare → load balancer → OxPHPTRUSTED_PROXIES=private では、信頼されるのはロードバランサーだけです。すると X-Forwarded-For の最も右側にある非信頼アドレスは、ロードバランサーに接続した Cloudflare のエッジになり、REMOTE_ADDR、アクセスログ、レート制限、client.address スパン属性のいずれでも、すべてのリクエストが Cloudflare から来たように見えます。両方のホップを信頼してください。
TRUSTED_PROXIES="private,cloudflare"ロードバランサーが private の範囲外のアドレスから接続してくる場合は、その範囲も列挙してください(TRUSTED_PROXIES="private,100.64.0.0/10,cloudflare")。
cloudflare キーワードを指定すると、OxPHP は 3 つのステップでクライアントアドレスを解決します。
- 1自分のプロキシを通り越して走査する。 OxPHP は接続元のピアから始めて、転送チェーン(
Forwardedがあればそれ、なければX-Forwarded-For)を左へたどり、自分のプロキシ、つまりprivateの範囲と列挙した CIDR を通り越します。Cloudflare の範囲内のアドレスは、列挙していても Cloudflare として扱われます。 - 2Cloudflare のホップを探す。 自分のプロキシではない最初のホップが Cloudflare のアドレスであれば、そのリクエストは Cloudflare を経由して届いたものであり、クライアントアドレスは
CF-Connecting-IPヘッダーになります。このヘッダーはちょうど 1 回だけ現れ、有効な IP アドレスを保持している必要があります。 - 3それ以外は最右端の非信頼。 リクエストが Cloudflare を経由していない場合、または
CF-Connecting-IPが欠けている、重複している、不正な形式である場合は、Cloudflare の範囲を信頼済みとして扱ったうえで、最右端の非信頼ルールが適用されます。
走査が止まるホップは、接続元のピアか、自分のプロキシのいずれかが記録したアドレスです。そのため、ロードバランサーに直接到達したクライアントは、自分で送った CF-Connecting-IP を OxPHP に読ませることができません。ロードバランサーはそこにクライアント自身のアドレスを記録し、クライアントがそれより左に書いた Cloudflare のアドレスには到達しないからです。最右端の非信頼ルールと同様に、これは OxPHP が読むヘッダーを自分のプロキシが維持していること、つまりクライアントの値をそのまま通すのではなく、接続してきたアドレスを追記していることを前提としています。
自分のプロキシは Forwarded も維持する必要があります。OxPHP は X-Forwarded-For より Forwarded を優先して読みますが、Forwarded は Cloudflare が設定するヘッダーではないため、Cloudflare は訪問者が送ったままの形で転送します。したがって、X-Forwarded-For だけを維持するロードバランサーは、リクエストから Forwarded を削除しなければなりません。このヘッダーをそのまま通すと、Cloudflare を経由するかどうかにかかわらず、また cloudflare キーワードの有無にかかわらず、どのクライアントでも OxPHP が見るアドレスを自由に選べてしまいます。
走査が Cloudflare のアドレスに到達する前に止まった場合、つまりクライアント IP の抽出で説明したように、アドレスを持たないエントリーや解析できないエントリー、または OxPHP がテキストとして読めない行で止まった場合も、CF-Connecting-IP は読み取られません。自分のプロキシのいずれかが独立した行に書いた記録は、それより前にクライアントが送った読めない行の影響を受けません。プロキシがクライアントの送ったテキストと同じ行に書いた記録は、その行とともに失われます。走査はそこで止まり、アドレスはその行より右側に自分のプロキシが記録したものから取得されるか、接続元のピアのままになります。クライアントが送ったものから取得されることは決してありません。
Cloudflare は自身に接続してきた相手のアドレスを X-Forwarded-For に追記するため、通常の訪問者であれば両方のヘッダーが同じクライアントを示します。両者が食い違うことがあるのは、Cloudflare Workers がオリジンに送るリクエストです。これらも、どのアカウントが Worker を所有しているかにかかわらず、Cloudflare のアドレスから送出されます。Cloudflare の HTTP ヘッダーのリファレンスには、そのようなリクエストが X-Forwarded-For に何を含めうるかは書かれていません。CF-Connecting-IP については書かれています。Worker がその値を選べるのは自身のゾーン内のホスト名へのリクエストに限られます。別の Cloudflare ゾーンのホスト名へのリクエストではヘッダーは固定アドレス 2a06:98c0:3600::103 になり、Cloudflare 上にないホスト名へのリクエストではクライアントのアドレスになり、Worker はそれを変更できません。
それでも Cloudflare の範囲を信頼することは、自分のゾーンだけでなく、Cloudflare のネットワークから出てくるすべてを信頼することを意味します。クライアントアドレスをセキュリティ上の判断に使う場合は、プラットフォームが提供する手段を使って、オリジンが自分のゾーンからのトラフィックだけを受け入れるようにしてください。
セキュリティ
- 安全なデフォルト —
TRUSTED_PROXIESがなければ、転送ヘッダーは一切処理されません - CIDR 検証 —
TRUSTED_PROXIESに無効な値があると起動時エラーになります - なりすまし耐性 — 最右端の非信頼アルゴリズムは、攻撃者が先頭に付け加えた値を無視します
- 信頼できない IP からのリクエストは、その転送ヘッダーが完全に無視されます
エージェント向けプロンプト
これらのプロンプトは、OxPHP ドキュメント MCP サーバー を接続したエージェントを前提としています — 必要なページはエージェントが自分で読み取ります。
私の OxPHP のデプロイ向けに信頼済みプロキシを設定してください。OxPHP ドキュメント MCP サーバー経由で `security/trusted-proxies` ページを読んでください。
私のインフラ関連のファイルから、アプリケーションの前段に何が置かれているのかを割り出したうえで、正確な設定を教えてください。そして、それぞれの値が何を受け入れるのか、また設定を間違えた場合にクライアントに何を偽装させてしまうのかも教えてください。