2026年8月21日

2026年8月21日

Nginxのログフォーマットをカスタマイズする方法(log_formatの書き方と実例)

Nginx のアクセスログは log_format で書式を定義し、access_log でその書式を指定するだけで自由に変えられます。この記事では、標準の combined 形式の中身を確認したうえで、応答時間や上流(PHP-FPM やバックエンド)の情報を足した書式、集計ツールに渡しやすい JSON 形式、条件付きでログを残す方法、CDN の背後で正しい接続元 IP を記録する方法までを順に設定できるようになります。

まず既定の書式を知る

access_log に書式名を指定しない場合、Nginx は組み込みの combined 形式を使います。中身は次のとおりで、Apache の combined 形式とほぼ同じ並びです。

log_format combined '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent"';

combined は定義済みなので、同じ名前で再定義しようとすると設定エラーになります。カスタマイズするときは main や timed のような別名を付けて新しく定義します。

また、log_format を書ける場所は http コンテキストだけです。server や location の中に書くと nginx -t で弾かれます。一方 access_log は http・server・location などに書けるので、「書式は http で定義し、どのログにどの書式を使うかはサイトごとに決める」という分担になります。

なお、エラーログ(error_log)の書式は log_format では変えられません。変えられるのは出力先とログレベルだけです。

応答時間と上流の情報を足した書式

WordPress の表示が遅いときに知りたいのは「どのリクエストが、Nginx と PHP のどちらで時間を使っているか」です。標準の書式にはこの情報がないため、次のような書式を追加しておくと調査が格段に楽になります。

# /etc/nginx/nginx.conf の http { } 内
log_format timed '$remote_addr - $remote_user [$time_local] '
                 '"$request" $status $body_bytes_sent '
                 '"$http_referer" "$http_user_agent" '
                 'rt=$request_time uct=$upstream_connect_time '
                 'uht=$upstream_header_time urt=$upstream_response_time '
                 'host=$host rid=$request_id';
# サイトの server { } 内
access_log /var/log/nginx/example.com.access.log timed;

それぞれの変数の意味は次のとおりです。

  • $request_time:Nginx がリクエストの最初の1バイトを読んでから、応答を送り終えるまでの時間(秒、ミリ秒単位)。クライアントの回線が遅い場合もここが伸びます。
  • $upstream_connect_time:上流(PHP-FPM やバックエンド)への接続にかかった時間。
  • $upstream_header_time:上流から応答ヘッダーを受け取るまでの時間。PHP の処理時間の目安になります。
  • $upstream_response_time:上流から応答をすべて受け取るまでの時間。
  • $request_id:リクエストごとに生成される一意の ID。アプリ側のログと突き合わせたいときに、fastcgi_param やヘッダーで渡して使えます。

$request_time が長いのに $upstream_response_time が短ければ、PHP ではなく転送側(回線や大きなファイル)に原因がある可能性が高い、というように切り分けができます。静的ファイルなど上流を通らないリクエストでは、上流系の変数は - になります。また、上流へのリトライが起きると値がカンマ区切りで複数並ぶことがあるので、集計スクリプトを書くときは数値1つとは限らない前提で扱ってください。

JSON 形式で出力する

Grafana Loki、Fluentd、Elasticsearch などにログを送る場合や、jq で集計したい場合は、JSON 形式にしておくとパースの手間がなくなります。escape=json を付けると、値の中のダブルクォートや制御文字が JSON として正しくエスケープされます。

log_format json_combined escape=json
  '{'
    '"time":"$time_iso8601",'
    '"remote_addr":"$remote_addr",'
    '"host":"$host",'
    '"request":"$request",'
    '"status":"$status",'
    '"bytes_sent":"$body_bytes_sent",'
    '"request_time":"$request_time",'
    '"upstream_response_time":"$upstream_response_time",'
    '"referer":"$http_referer",'
    '"user_agent":"$http_user_agent",'
    '"request_id":"$request_id"'
  '}';

access_log /var/log/nginx/example.com.json.log json_combined;

数値の項目もあえて文字列(ダブルクォートで囲む)にしているのは、前述のとおり上流系の変数が - やカンマ区切りになることがあり、そのまま数値として出すと JSON として壊れた行ができるからです。型の変換は取り込む側で行うほうが安全です。

確認は tail -n 5 /var/log/nginx/example.com.json.log | jq . のように、実際に数行をパースしてみるのが早道です。エラーが出る行があれば、その行に含まれる値を見て書式を直します。

条件付きでログを残す・残さない

監視ツールのヘルスチェックや静的ファイルのアクセスがログの大半を占めると、本当に見たい行が埋もれます。access_log の if= パラメータと map を組み合わせると、条件に合うリクエストだけを記録できます。

# http { } 内:2xx と 3xx は記録しない(エラーだけ残す例)
map $status $loggable {
    ~^[23]  0;
    default 1;
}

# server { } 内
access_log /var/log/nginx/example.com.error-only.log timed if=$loggable;

if= の値が空文字か 0 のときは記録されません。通常のアクセスログと、エラーだけのログを2本並べて出す、といった使い方もできます。

静的ファイルだけを除外したい場合は、該当する location の中で access_log off; を指定するほうが分かりやすいでしょう。ただし、攻撃や不正アクセスの調査では静的ファイルへのアクセスも手がかりになることがあるため、何でも消すのではなく「何のためのログか」を決めてから減らしてください。

CDN やロードバランサーの背後で接続元 IP を正しく記録する

Cloudflare などの CDN やロードバランサーを経由していると、$remote_addr には中継サーバーの IP が入り、訪問者の IP が分かりません。ngx_http_realip_module(多くのディストリビューションのパッケージで有効)を使い、信頼できる中継元からのヘッダーだけを採用します。

# 中継サーバーの IP 範囲(例。実際の範囲は各サービスの公開情報で確認)
set_real_ip_from 192.0.2.0/24;
real_ip_header   X-Forwarded-For;
real_ip_recursive on;

set_real_ip_from を広く設定しすぎると、誰でも X-Forwarded-For を偽装して好きな IP をログに残せてしまいます。CDN の IP 範囲は変わることがあるため、公式に公開されている一覧を定期的に確認して更新する運用が必要です。主要な CDN は自社の IP 範囲を公開しています(2026年10月現在)。置き換え前の IP も残したい場合は、$realip_remote_addr を書式に加えておくと便利です。

反映手順と確認方法

書式の変更に Nginx の再起動(restart)は要りません。次の順で進めると、設定ミスでサイトが止まる事故を避けられます。

  1. 設定ファイルをバックアップしてから編集する
  2. sudo nginx -t で構文を確認する
  3. sudo systemctl reload nginx で設定を再読み込みする(接続を切らずに反映されます)
  4. サイトに数回アクセスし、tail -f で新しい書式の行が出ているか確認する

ログファイル名を新しくした場合は、logrotate の対象パターンに含まれているかも確認してください。多くのディストリビューションでは /var/log/nginx/*.log が対象になっていますが、別のディレクトリに出すとローテーションされずにディスクを圧迫することがあります。

個人情報とログの扱い

アクセスログには IP アドレスやユーザーエージェントが残り、リクエスト URL のクエリ文字列にはメールアドレスやトークンが含まれることもあります。WordPress のパスワードリセットのリンクにもキーがクエリとして含まれます。項目を増やすときは、本当に必要な情報かを考え、保存期間を決めて logrotate で古いログを消す、閲覧できるユーザーを限定する、といった管理もあわせて行ってください。Cookie や Authorization ヘッダーを丸ごとログに出す設定は避けるべきです。

まとめ

  • 書式は http コンテキストの log_format で定義し、access_log で指定する。combined は定義済みなので別名を付ける
  • 遅延調査には $request_time と $upstream_*_time を加えると、Nginx 側か PHP 側かを切り分けやすい
  • 集計ツールに渡すなら escape=json の JSON 形式。値は文字列で出すと行が壊れにくい
  • map と if= で残すログを絞れる。CDN 背後では realip モジュールで信頼できる中継元だけを採用する
  • 反映前に nginx -t、反映は reload。ログに含まれる個人情報と保存期間にも注意する

お気軽にご相談ください

お見積りへ お問い合わせへ
▲