
nginx-proxyとApacheで実IPアドレスを取得する方法(Apache設定とログフォーマット編)
シリーズ最終回!nginx-proxyから X-Real-IP
ヘッダーが送られてきても、Apache側で正しく設定しないとログに記録されません。この記事では、mod_remoteipの設定とログフォーマットの詳細を解説します。
Apache側の設定:mod_remoteip
Apacheはデフォルトでは X-Real-IP
ヘッダーを使ってくれません。mod_remoteip
という専用のモジュールで「このヘッダーを信頼して、ログに記録してね」と教える必要があります。
<IfModule mod_remoteip.c>
RemoteIPHeader X-Real-IP # このヘッダーを信頼する
RemoteIPTrustedProxy 172.18.0.0/16 # このIP範囲からの接続を信頼
RemoteIPInternalProxy 172.18.0.0/16 # 内部プロキシとして扱う
</IfModule>
172.18.0.0/16
はDockerの内部ネットワークのIP範囲です。nginx-proxyコンテナは 172.18.0.4
なので、この範囲に含まれます。
ログフォーマットの罠
mod_remoteip
を有効化しただけでは、まだログに内部IPが出てきます。ログフォーマットも変更する必要があるんです!
%h と %a の違い
%h
: 接続してきたホストのIP(デフォルト)%a
:mod_remoteip
が解決した実際のクライアントIP
デフォルトの combined
フォーマットは %h
を使っています:
LogFormat "%h %l %u %t "%r" %>s %O ..." combined
↑ これを %a に変更する必要がある
正しい設定方法
remoteip.conf
というファイルを作成します:
# remoteip.conf
# %a で実IPを記録
LogFormat "%a %l %u %t "%r" %>s %O "%{Referer}i" "%{User-Agent}i"" combined
そしてDockerfileで:
# mod_remoteipを有効化
RUN a2enmod remoteip
docker-compose.ymlでマウント:
volumes:
- ./remoteip.conf:/etc/apache2/conf-available/remoteip.conf:ro
カスタムエントリーポイントで有効化:
if [ -f /etc/apache2/conf-available/remoteip.conf ]; then
echo "Enabling remoteip.conf..."
a2enconf remoteip 2>/dev/null || true
fi
ログフォーマット指定子の詳細解説
実際のログを見ながら、各フォーマット指定子を理解しましょう:
122.249.239.79 - test [14/Oct/2025:16:43:34 +0900] "GET / HTTP/1.1" 200 2800 "-" "Mozilla/5.0..."
%a – 実際のクライアントIP
%a = 122.249.239.79
mod_remoteip
が解決した実際のクライアントIP%h
を使うと172.18.0.4
になってしまう- 今回の設定で一番重要なポイント!
%l – リモートログ名
%l = -
- RFC 1413 の identd プロトコルによるリモートログ名
- ほとんどの場合
-
(取得できない) - 現代ではほぼ使われていない
%u – 認証ユーザー名
%u = test
- Basic認証やDigest認証でログインしたユーザー名
- 未認証の場合は
-
と表示される
例:
# 未認証(401エラー)
172.18.0.4 - - [14/Oct/2025:16:40:39 +0900] "GET / HTTP/1.1" 401 698
↑ ユーザー名が "-"
# 認証成功
122.249.239.79 - test [14/Oct/2025:16:43:34 +0900] "GET / HTTP/1.1" 200 2800
↑ "test" でログイン済み
%t – リクエスト受信時刻
%t = [14/Oct/2025:16:43:34 +0900]
[日/月/年:時:分:秒 タイムゾーン]
の形式+0900
は JST(日本標準時)
%r – リクエスト行
%r = "GET / HTTP/1.1"
- クライアントが送信したリクエストの最初の行
- フォーマット:
メソッド パス プロトコル
- 例:
GET / HTTP/1.1
– トップページを取得POST /login HTTP/1.1
– ログインフォームを送信GET /images/logo.png HTTP/1.1
– 画像を取得
%>s – 最終ステータスコード
%>s = 200
>
は「最終的な」という意味- 主なステータスコード:
200
– OK(成功)401
– Unauthorized(認証が必要)403
– Forbidden(アクセス禁止)404
– Not Found(ページが見つからない)500
– Internal Server Error(サーバーエラー)
%s
だと内部リダイレクトの途中のステータスも記録されてしまうので、%>s
を使うのが正しいです。
%O – 送信バイト数
%O = 2800
- レスポンス全体のサイズ(HTTPヘッダー + ボディ)
- 大文字の
O
(オー)に注意! - 類似の指定子:
%b
– ボディのみ(0の場合は-
)%B
– ボディのみ(0の場合は0
)%O
– ヘッダー + ボディ(推奨)
実際のログを解読してみよう
122.249.239.79 - test [14/Oct/2025:16:43:34 +0900] "GET / HTTP/1.1" 200 2800 "-" "Mozilla/5.0..."
部分 | 指定子 | 意味 |
---|---|---|
122.249.239.79 | %a | 実際のクライアントIP |
- | %l | リモートログ名(取得できず) |
test | %u | Basic認証のユーザー名 |
[14/Oct/2025:16:43:34 +0900] | %t | リクエスト日時 |
"GET / HTTP/1.1" | %r | リクエスト行 |
200 | %>s | ステータスコード(成功) |
2800 | %O | 送信バイト数(約2.8KB) |
"-" | %{Referer}i | リファラー(なし) |
"Mozilla/5.0..." | %{User-Agent}i | ブラウザ情報 |
読み方:
IPアドレス 122.249.239.79 からのアクセス。ユーザー名 “test” で認証済み。2025年10月14日 16:43:34(日本時間)に、トップページ(/)を HTTP/1.1 プロトコルで GET リクエストした。サーバーは 200(成功)を返し、2800バイトのデータを送信した。
修正前後の比較
修正前:
172.18.0.4 - test [14/Oct/2025:16:20:02 +0900] "GET /favicon.ico HTTP/1.1" 404 443
↑ 内部IP(全員同じ)
修正後:
122.249.239.79 - test [14/Oct/2025:16:43:34 +0900] "GET / HTTP/1.1" 200 2800
↑ 実IP(訪問者ごとに異なる)
%a
に変えただけで、こんなに変わります。たった1文字の違いですが、セキュリティやアクセス解析にとって非常に重要な違いなんです。
静的設定 vs ランタイム設定
apachectl -t -D DUMP_RUN_CFG | grep LogFormat
を実行しても何も表示されないのはなぜでしょうか?
静的設定(Static Configuration)
ファイルに書かれた設定で、Apacheの起動時に読み込まれる:
LogFormat "%a %l %u %t "%r" %>s %O" combined
ServerName localhost
DocumentRoot /var/www/html
- 起動時に1回だけ読み込む
- 変更するには再起動が必要
LogFormat
はこちらに該当
ランタイム設定(Runtime Configuration)
Apacheが動作中に変化する値:
$remote_addr = 122.249.239.79 ← リクエストごとに変わる
$request_time = 0.234秒 ← リクエストごとに変わる
現在の接続数 = 45 ← 時間とともに変わる
DUMP_RUN_CFG
で表示されるのは、こういう「今の状態」に関する情報です。LogFormat
は起動時に読み込まれるテンプレートなので、動作中の状態をダンプするコマンドには出てこないんです。
確認したいもの | コマンド |
---|---|
静的設定 | grep LogFormat /etc/apache2/*.conf |
ランタイム状態 | apachectl -t -D DUMP_VHOSTS |
モジュールの状態 | apachectl -M |
実際の動作結果 | tail /var/log/apache2/access.log |
設定の反映方法
docker-compose.yml でボリュームマウントを追加した場合は、restart
ではなく再作成が必要です:
# ❌ これではボリュームマウントは更新されない
docker compose restart apache
# ✅ これなら新しいマウント設定が反映される
docker compose down
docker compose up -d
まとめ:完璧な設定
nginx-proxy側
# docker-compose.yml
environment:
- TRUST_DOWNSTREAM_PROXY=false # 安全側に設定
# custom.conf
map $http_cf_connecting_ip $real_client_ip {
default $http_cf_connecting_ip; # CF経由なら実IP
"" $remote_addr; # 直接なら接続元IP
}
proxy_set_header X-Real-IP $real_client_ip;
Apache側
# Dockerfile
RUN a2enmod remoteip
# remoteip.conf
LogFormat "%a %l %u %t "%r" %>s %O "%{Referer}i" "%{User-Agent}i"" combined
# apache.conf
<IfModule mod_remoteip.c>
RemoteIPHeader X-Real-IP
RemoteIPTrustedProxy 172.18.0.0/16
RemoteIPInternalProxy 172.18.0.0/16
</IfModule>
# docker-compose.yml
volumes:
- ./remoteip.conf:/etc/apache2/conf-available/remoteip.conf:ro
これで、Cloudflare経由でも直接アクセスでも、偽装攻撃を防ぎつつ安全に実IPアドレスを取得できます!
シリーズ完結
3回にわたって、nginx-proxy + Apache環境での実IP取得方法を解説しました。
- 第1部:HTTPヘッダーの基礎とnginx変数
- 第2部:TRUST_DOWNSTREAM_PROXYと混在環境での解決策
- 第3部:Apache設定とログフォーマットの詳細(この記事)
この設定で、セキュリティを保ちながら正確なアクセスログを残せるようになります。困ったことがあれば、コメント欄でお気軽にご質問ください!
タグ: Apache, mod_remoteip, LogFormat, アクセスログ, WordPress, Docker, 静的設定, ランタイム設定