手帳

  • 2025-10-18
  • IT系

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%uBasic認証のユーザー名
[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, 静的設定, ランタイム設定