2026年5月25日
2026年5月25日
Nginxのログ形式をカスタマイズする方法|log_formatの書き方とJSON・条件付きログ
Nginx のアクセスログの形式は、log_format で名前を付けて定義し、access_log でその名前を指定して使います。既定の形式(combined)には応答にかかった時間やバックエンドの情報が含まれないため、遅いリクエストの調査や監視システムへの取り込みには、項目を足した形式を作ります。
この記事の設定は、Nginx 1.30.6(nginx.org の公式 Docker イメージ)で nginx -t と実際のログ出力を確認しました(2026年11月時点)。ディレクティブの仕様は公式ドキュメント(ngx_http_log_module)に基づいています。
log_format と access_log の基本
| ディレクティブ | 書ける場所 | 役割 |
|---|---|---|
log_format 名前 [escape=default/json/none] 文字列 ...; |
http ブロックだけ |
ログ形式を定義する |
access_log パス [形式名] [buffer=] [flush=] [if=]; |
http・server・location など |
どのファイルにどの形式で書くかを指定する |
log_format を server や location の中に書くと、nginx -t が次のエラーを出します。
nginx: [emerg] "log_format" directive is not allowed here in /etc/nginx/conf.d/bad.conf:1
access_log で形式名を省略すると、あらかじめ定義されている combined が使われます。combined の中身は次のとおりです。
log_format combined '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"';
combined という名前は組み込みなので、同じ名前で定義し直すことはできません。独自の形式には別の名前を付けます。
設定ファイルを置く場所
log_format は http ブロックの中に入る必要があります。Debian/Ubuntu のパッケージや nginx.org の公式パッケージでは、/etc/nginx/nginx.conf の http ブロックに include /etc/nginx/conf.d/*.conf; があるので、conf.d にファイルを置けば http ブロックに書いたのと同じになります。念のため確認します。
grep -n "include" /etc/nginx/nginx.conf
ここでは /etc/nginx/conf.d/log_format.conf に形式を定義し、各サイトの server ブロックで access_log を指定します。設定ファイルの編集はエディターで行ってください。echo "... $remote_addr ..." > ファイル のようにシェルの二重引用符で書くと、$remote_addr などがシェルの変数として展開されて消えてしまいます。
応答時間とバックエンドの情報を足した形式
combined の後ろに、ホスト名・処理時間・バックエンド(proxy_pass や fastcgi_pass の接続先)の情報を足した例です。
# /etc/nginx/conf.d/log_format.conf
log_format main_ext '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" "$http_user_agent" '
'host=$host rt=$request_time '
'uct=$upstream_connect_time urt=$upstream_response_time '
'ua=$upstream_addr us=$upstream_status';
# 各サイトの server ブロック
server {
server_name example.com;
access_log /var/log/nginx/example.access.log main_ext;
# ...
}
確認環境では次のように出力されました。バックエンドを通らないリクエストでは $upstream_* が - になります。
127.0.0.1 - - [10/Nov/2026:09:32:37 +0000] "GET /app/x HTTP/1.1" 200 8 "http://ref/?a=b" "Mozilla \x22quote\x22 test" host=127.0.0.1 rt=0.000 uct=0.000 urt=0.000 ua=127.0.0.1:8080 us=200
127.0.0.1 - - [10/Nov/2026:09:32:37 +0000] "GET / HTTP/1.1" 200 3 "-" "curl/8.14.1" host=127.0.0.1 rt=0.000 uct=- urt=- ua=- us=-
既定のエスケープ(escape=default)では、" や \、制御文字などが \x22 のような形に置き換わります。
よく使う変数
| 変数 | 内容 |
|---|---|
$remote_addr |
接続元の IP アドレス(ロードバランサーなどの後ろでは、その機器のアドレスになる) |
$http_x_forwarded_for |
リクエストの X-Forwarded-For ヘッダー |
$time_local / $time_iso8601 |
時刻(Common Log Format 形式 / ISO 8601 形式) |
$msec |
ログを書いた時刻(ミリ秒単位の UNIX 時間) |
$request |
リクエスト行全体(例: GET /path HTTP/1.1) |
$request_method / $request_uri |
メソッド / 引数を含む URI |
$host |
リクエストのホスト名 |
$status |
応答のステータスコード |
$body_bytes_sent / $bytes_sent |
送った本文のバイト数 / ヘッダーを含む全体のバイト数 |
$request_length |
受け取ったリクエストの長さ(ヘッダーと本文を含む) |
$request_time |
最初のバイトを受け取ってから、最後のバイトを送り終えるまでの秒数(ミリ秒単位) |
$upstream_connect_time |
バックエンドとの接続にかかった秒数 |
$upstream_header_time |
バックエンドから応答ヘッダーを受け取るまでの秒数 |
$upstream_response_time |
バックエンドから応答を受け取り終えるまでの秒数 |
$upstream_addr / $upstream_status |
実際に使ったバックエンドのアドレス / その応答のステータス |
$request_id |
リクエストごとに生成される 16 バイトの ID(16 進 32 文字) |
$ssl_protocol / $ssl_cipher |
TLS のバージョン / 暗号スイート(HTTPS のとき) |
$upstream_* は、バックエンドを複数試した場合に 0.001, 0.002 のようにカンマ区切りで複数の値が入ります。
$request_time が大きく $upstream_response_time が小さいときは、バックエンドではなくクライアントへの送信(回線が遅い、ファイルが大きいなど)に時間がかかっています。両方が大きいときは、バックエンド側の処理を調べます。
JSON 形式で出す(escape=json)
ログ収集の仕組み(Elasticsearch、Loki、クラウドのログサービスなど)に取り込む場合は、JSON 形式にしておくと項目の切り出しが不要になります。escape=json を付けると、値の中の " や \ が JSON として正しい形にエスケープされ、値が無い変数は空文字になります。
# /etc/nginx/conf.d/log_format.conf
log_format json_combined escape=json
'{'
'"time":"$time_iso8601",'
'"remote_addr":"$remote_addr",'
'"host":"$host",'
'"method":"$request_method",'
'"uri":"$request_uri",'
'"protocol":"$server_protocol",'
'"status":$status,'
'"bytes_sent":$body_bytes_sent,'
'"referer":"$http_referer",'
'"user_agent":"$http_user_agent",'
'"request_time":$request_time,'
'"upstream_response_time":"$upstream_response_time",'
'"request_id":"$request_id"'
'}';
確認環境での出力です(1 リクエスト 1 行)。
{"time":"2026-11-10T09:32:37+00:00","remote_addr":"127.0.0.1","host":"127.0.0.1","method":"GET","uri":"/app/x","protocol":"HTTP/1.1","status":200,"bytes_sent":8,"referer":"http://ref/?a=b","user_agent":"Mozilla \"quote\" test","request_time":0.000,"upstream_response_time":"0.000","request_id":"5c4f1ceb37b553029aa364001c192a6c"}
$status・$body_bytes_sent・$request_time は常に数値なので、引用符を付けずに数値として出しています。$upstream_response_time は値が無いと空になり、複数の値がカンマ区切りで入ることもあるため、文字列として扱っています。
条件付きでログを書く・書かない
エラーの応答だけを別のファイルに書く
access_log の if= に変数を指定すると、その値が空でも 0 でもないときだけ書きます。map は http ブロックに書きます。
# /etc/nginx/conf.d/log_format.conf
map $status $loggable {
~^[23] 0;
default 1;
}
# server ブロック
access_log /var/log/nginx/example.access.log main_ext;
access_log /var/log/nginx/example.error-status.log main_ext if=$loggable;
同じブロックに access_log を複数書くと、すべてのファイルに書かれます。確認環境では、2xx・3xx の応答は 1 つ目のファイルだけに、404 の応答は両方のファイルに記録されました。
ヘルスチェックなどを記録しない
location = /healthz {
access_log off;
return 200 "ok\n";
}
下位のブロック(location など)に access_log を書くと、上位のブロックの access_log は引き継がれず、その location の指定だけが使われます。
バッファーで書き込み回数を減らす
アクセスが多いサーバーでは、buffer= を付けるとリクエストごとにディスクへ書かず、まとめて書き込みます。
access_log /var/log/nginx/example.json.log json_combined buffer=32k flush=5s;
buffer=32k:32KB たまったら書き込むflush=5s:たまっていなくても 5 秒経ったら書き込む
書き込みが遅れるので、tail -f で見ていてもすぐには表示されません。バッファーはワーカープロセスごとに持つため、確認環境では行の並びが時刻の順と前後することがありました。
反映と確認
sudo nginx -t
sudo systemctl reload nginx
curl -s -o /dev/null http://example.com/
sudo tail -n 3 /var/log/nginx/example.access.log
reload で反映できます。再起動(restart)は不要です。新しいログファイルは Nginx が自動で作ります。
ステータスコードごとの件数は次のように数えられます(combined と main_ext では 9 番目の項目がステータスコード)。
awk '{print $9}' /var/log/nginx/example.access.log | sort | uniq -c
ログのローテーション
Debian/Ubuntu のパッケージでは /etc/logrotate.d/nginx が入っており、/var/log/nginx/*.log が毎日ローテーションされ 14 世代保存されます(Debian 13 のパッケージで確認)。新しく作るログファイルも /var/log/nginx/ に .log の名前で置けば対象になります。別の場所に置く場合は、logrotate の設定に追加します。ローテーション後に Nginx に新しいファイルを開かせるには、nginx -s reopen を使います。
注意点
- ログには IP アドレスや URL の引数(検索語、トークンなど)が残ります。閲覧できるユーザーを限り、保存期間を決めておきます。
- 形式を変えると、ログを読む側のツール(GoAccess、awk のスクリプト、監視の設定など)が項目の位置を誤ることがあります。既存のファイル名の形式を変えるより、別の名前のファイルに書き始めるほうが安全です。
- エラーログ(
error_log)の形式は変えられません。変えられるのはアクセスログだけです。
ログを集計して見るには GoAccessでNginxのアクセスログを分析する方法、条件の書き方は Nginxのmapモジュールの使い方 を参照してください。