Apache mod_remoteipの設定:X-Forwarded-For・信頼するプロキシとIP確認

mod_remoteipは転送ヘッダーを受け取るだけでは不十分で、信頼するプロキシとログに残す接続元をセットで設定します。

ApacheのアクセスログがロードバランサーやCDNのIPばかりになる場合、Apache 2.4のmod_remoteipで訪問者のIPを扱えます。基本はRemoteIPHeaderと信頼する中継元の指定です。ヘッダーだけを有効にした例をそのまま公開サーバーへ入れると、利用者が送った値まで信用してしまう場合があります。

知りたいこと先に確認する箇所
mod_remoteipとはリクエスト単位でApacheが扱うクライアントIPを置き換えるモジュール
有効になっているか実際のApacheと同じ設定で-Mを確認
X-Forwarded-Forを使う設定RemoteIPHeaderと、実際の中継元だけを指定するInternalProxy/TrustedProxy
設定したのにIPが変わらない到着ヘッダー、中継元、対象VirtualHost、%aと%{c}aを比較
CloudflareのIPになるCF-Connecting-IPと現在のCloudflare接続元一覧を使う別構成

以下は構成例です。10.0.0.5などの内部アドレスは管理下のプロキシ例、203.0.113.5や198.51.100.77は文書用アドレスです。実環境へコピーする前に置き換えてください。この記事のApache/NGINX設定は構文と公式仕様を照合したもので、読者のサーバーで稼働検証した結果ではありません。

目次

mod_remoteipで変わるIPと変わらないIP

mod_remoteipはTCP接続そのものを訪問者からの接続へ変える機能ではありません。ApacheがログやRequire ipなどの判定に使うIPを、信頼できる中継情報からリクエストごとに取り出します。ログの%aは処理後のクライアントIP、%{c}aは実際に接続した相手のIPです。まず両方を残すと、転送経路と判定結果を比較できます。

1. 使用しているApacheとモジュールを確認する

apachectlを使う環境では、バージョンと読み込むモジュールを確認します。サービスが別の-f設定や別インスタンスで動いている場合は、その起動設定に合わせて実行してください。Apache 2.4を対象にし、古い2.2へ追加コンパイルする案内とは分けます。

apachectl -v
apachectl -M

読み込まれている場合の表示例です。実際の出力で確認してください。

remoteip_module (shared)

Debian系のapache2パッケージで、モジュールが利用可能だが未有効の場合にはa2enmodを使います。これは設定を変更する操作なので、管理者がバックアップと適用タイミングを確認して行います。RHEL系や独自ビルドへ共通の導入コマンドとして使わないでください。

sudo a2enmod remoteip

Debianの標準パッケージではmods-enabledのリンクで有効化されます。読み込みファイルの内容を確認する例です。ファイルがなければパッケージと構成を先に調べます。

cat /etc/apache2/mods-enabled/remoteip.load

LoadModuleを直接管理する構成での記述形は次のとおりです。modules/mod_remoteip.soは実際の配置に合わせます。有効化済みの設定へ二重に追加する必要はありません。

LoadModule remoteip_module modules/mod_remoteip.so

httpdという実行名を使う環境では次の確認ができます。設定の場所が独自なら-f等も起動条件に合わせます。-Mは静的/共有モジュールの一覧、-tは設定ファイルの構文検査です。サービスの再起動は行いません。

httpd -v
httpd -M
httpd -t

複数サイトがある場合はVirtualHostの対応も確認します。-SはVirtualHost設定の表示であり、すべての実行時動作を検証する機能ではありません。

apachectl -S

2. X-Forwarded-Forと信頼する中継元を指定する

管理下のロードバランサー10.0.0.5がX-Forwarded-Forを付け、内部クライアントのプライベートIPも扱う設計なら次の例です。RemoteIPInternalProxyへ記すのは訪問者全体ではなく、情報を信用してよい中継元です。対象サーバーまたはVirtualHostの既存設定を確認して追加します。

RemoteIPHeader X-Forwarded-For
RemoteIPInternalProxy 10.0.0.5

管理下の中継が2台なら空白区切りで列挙できます。10.0.0.6も本当にその役割の機器である場合の例です。カンマ区切りの引数や、念のため内部ネットワーク全域を指定する運用は避けます。

RemoteIPInternalProxy 10.0.0.5 10.0.0.6

プライベートなクライアントIPまで信用する必要がない外向けの構成ではRemoteIPTrustedProxyを検討します。TrustedProxy自身が内部アドレスであることは可能です。InternalProxyとの違いは、ヘッダー中のプライベートなクライアントIPも信用するかどうかです。次は前のInternalProxyと併記する一括設定ではなく、要件に応じて選ぶ別例です。

RemoteIPHeader X-Forwarded-For
RemoteIPTrustedProxy 10.0.0.5

RemoteIPHeaderだけがあり、InternalProxy/TrustedProxyの制限がない構成では、Apache公式仕様上、ヘッダーを提示するすべてのホストを信用します。正しいIPの形で書かれていても送信者が信用できるとは限りません。SetEnvIfでIPv4らしい文字列を拾うだけでは、この信頼境界の代わりになりません。IPv6や複数中継にも対応できなくなります。

3. 複数IPは右側から信頼関係を確認する

X-Forwarded-Forは常に一番左を無条件で採用するわけではありません。mod_remoteipは右から左へ処理し、そのIPを提示した相手を信用できなくなったところで止まります。次は管理下の2段プロキシ構成を説明する例です。

X-Forwarded-For: 203.0.113.5, 10.0.0.6
TCP接続元: 10.0.0.5

10.0.0.5と10.0.0.6をInternalProxyで信用する設計なら、想定する比較結果は次のとおりです。これは実測ログではありません。途中の信頼指定が欠ければ、必ず203.0.113.5までさかのぼるとは限りません。

クライアントIP %a: 203.0.113.5
実際の接続元 %{c}a: 10.0.0.5

処理後の転送ヘッダーには確認できなかった部分だけが残り、全部確認できた場合はヘッダー自体が削除されます。そのため%{X-Forwarded-For}iを後段のログへ入れても、到着時の生ヘッダーを必ず保存できるわけではありません。

4. %aと接続元IPを同じログで検証する

既存ログへ影響を与えない診断用フォーマットの例です。remoteip-proxy-ip-listはモジュールが処理した中継情報のノートで、未加工ヘッダーとは区別します。

LogFormat "client=%a peer=%{c}a proxies=%{remoteip-proxy-ip-list}n \"%r\" %>s" remoteip_diag

LogFormatに名前を付けるだけではログへ出ません。対象VirtualHost等のCustomLogから参照します。保存先はServerRootに対する相対パスの例です。実際のパッケージで使う場所・書き込み権限・ログ保持方針に合わせます。

CustomLog "logs/remoteip_diag.log" remoteip_diag

想定する1行の例です。アクセス元・時刻・リクエストと照らし合わせて、対象サイトのログか確認します。

client=203.0.113.5 peer=10.0.0.5 proxies=- "GET /ip-check HTTP/1.1" 200

確認時はCustomLog/ErrorLogの実際の場所を読みます。次の2例はパッケージ既定のログを読む形であり、診断ログを必ずこの場所へ書く設定ではありません。既存ログへ%aと%{c}aを加えた場合にも利用できます。

tail -n 30 /var/log/apache2/access.log
tail -n 30 /var/log/httpd/error_log

5. NGINX側のヘッダー生成も確認する

Apache側だけを変更しても、中継が必要な情報を渡さなければ復元できません。NGINXが最初の公開入口で、手前にCDNや別の信頼済みプロキシがない構成では、利用者が持ち込んだX-Forwarded-Forを引き継がず、接続元から設定する設計が可能です。適用先は実際にApacheへ渡すlocation等です。

proxy_set_header X-Forwarded-For $remote_addr;

管理された複数中継で既存チェーンを残す場合の別例です。$proxy_add_x_forwarded_forは到着したヘッダーへ接続元を追加するので、先頭の任意値を自動的に安全な値へ変換する機能ではありません。CDNが手前にあるのに前例で上書きすると、その前の訪問者情報を失うことがあります。構成とApache側の信頼範囲を一緒に決めます。

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

NGINX設定を編集した場合は、適用前に構文検査を行います。成功しても実際の転送内容まで確認したことにはなりません。

nginx -t

6. 任意ヘッダーを信用しないことを確かめる

管理下の最初のプロキシが接続元203.0.113.5を追記し、その利用者が任意に198.51.100.77を送った例です。Apacheへ届いたヘッダーが次の形なら、右側の203.0.113.5自身は信頼するプロキシではないため、そのさらに左の任意値までクライアントとして採用しないことを確認します。

X-Forwarded-For: 198.51.100.77, 203.0.113.5

ヘッダー指定テストは所有する検証用エンドポイントだけで行います。次の.invalidドメインは動作しない置換用例です。上流がヘッダーを上書きする場合と追記する場合を分け、Apacheの診断ログを見ます。curl -Iで応答ヘッダーだけを見ても、Apacheへ届いたリクエストヘッダーの確認にはなりません。

curl -H "X-Forwarded-For: 198.51.100.77" "https://proxy-test.example.invalid/ip-check"

直接到達を許している検証経路がある場合は、信頼するプロキシ以外から同じ値を送っても%aがその値にならないことを確認します。検証のために本番のオリジンを新しく外部へ開放する必要はありません。

curl -H "X-Forwarded-For: 198.51.100.77" "https://origin-test.example.invalid/ip-check"

信頼していない直アクセスで想定する関係です。直接接続者の実IPはテスト環境ごとに異なります。

%a と %{c}a は直接接続者のIP
%a が任意指定の 198.51.100.77 にならないことを確認

7. 反映されない場合は設定の対象を確認する

Apache 2.4公式ではRemoteIPHeaderなどはserver config/virtual hostの設定です。.htaccessへ置く設定ではありません。同じ名前のサイトや別ポートのVirtualHost、既存設定との重複を確認します。Debianの標準設定フォルダーを読む例で、独自構成では実際の設定場所へ置き換えます。

grep -R -n -E "RemoteIP(Header|InternalProxy|TrustedProxy)|remoteip_diag" /etc/apache2/

編集後は構文検査です。Invalid commandが出る場合はモジュール未読込や誤った実行設定を確認し、エラーのまま反映しません。Syntax OKは、信頼するIPの選び方や認可が正しいことの保証ではありません。

apachectl configtest

管理者が変更内容と影響を確認した後、構成で決められたreload手順を使います。apachectlで制御するサーバーの例ではgracefulです。開いている接続を中断しない再読込ですが、停止中のサーバーを起動する場合もあるため、状態と変更時間を先に確認します。別サービス名を両方再起動する必要はありません。

apachectl graceful

モジュール一覧を再確認したうえで、新しいリクエストのログを検証します。古いログ行のIPは設定変更だけで書き換わりません。

apachectl -M

8. Cloudflareなら専用ヘッダーと接続元一覧を使う

Cloudflare公式はApacheで訪問者IPを復元する方法としてmod_remoteipを案内しています。ApacheへCloudflareから直接届く構成でのヘッダー指定は次のとおりです。X-Forwarded-Forの前例をそのまま追加する一括設定ではなく、構成に合わせた別設定です。

RemoteIPHeader CF-Connecting-IP

信頼する接続元には現在のCloudflare IP範囲を使います。公式一覧から管理者が確認・管理するファイルを用意する方式の記述例です。このパスにファイルがないまま適用しないでください。手前に別ロードバランサーがある場合は、実際の接続元とヘッダーの受け渡しをさらに確認します。

RemoteIPTrustedProxyList conf/trusted-cloudflare.lst

リストは1行1アドレス/CIDRです。次は内容の書式説明だけで、有効な接続元一覧ではありません。静的なサンプルIPをCloudflareの実アドレスとして信用しないでください。

# このファイルには確認済みの現在のCloudflare IPv4/IPv6範囲を1行ずつ記す
# 空のまま本番へ適用しない

CF-Connecting-IPが届かない場合はTransform Rulesなどのヘッダー変更も確認します。Pseudo IPv4のOverwrite Headersを使う構成では値が変わる条件があるため、常に同じ種類のIPになると決めつけません。反映前の構文検査と、反映後の%a/%{c}aの比較は同じです。

apachectl configtest

アクセス制限と変更の戻し方

mod_remoteipはRequire ipが見るIPにも影響します。プロキシ接続元を許可するネットワーク境界の設定と、復元した訪問者IPを使うアプリケーション認可は別に確認してください。Require ipで内部アドレスを指定しただけで、転送ヘッダーの信頼元を限定したことにはなりません。

問題が出たら、追加したRemoteIP設定と診断ログ設定の変更範囲をバックアップとの差分で確認し、管理手順で元へ戻します。次は差分を読むだけの例です。2つのパスは自分の変更前ファイルと現在のファイルへ置き換えます。

diff -u /path/to/remoteip.before.conf /path/to/remoteip.current.conf

戻した設定も適用前に構文を確認し、管理手順で反映した後に対象VirtualHostの通信とログを確認します。

apachectl configtest

SELinuxのhttpd_can_network_connectを一律で有効にする、許可ルールを追加すれば他を遮断できると考える、全パッケージを一括更新する、といった操作はIP復元の確認手順には含めません。

確認資料

製品の概要はApache HTTP Server公式サイトを参照できます。設定の適用範囲とログの項目は以下の一次資料で確認してください。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次