Claude Media
Claude CodeでNginxのアクセスログを解析 — 5xxの急増を時間帯・パス・UAで切り分ける

Claude CodeでNginxのアクセスログを解析 — 5xxの急増を時間帯・パス・UAで切り分ける

Nginxのアクセスログから5xxの急増を、時間帯・パス・User-Agentの順に絞り込む手順です。stdin 10MBの上限への対処と、awkの集計結果で検算する方法を示します。

Nginxの5xxが急に増えたとき、最初に知りたいのは「いつから」「どのパスで」「誰のリクエストで」の3点です。アクセスログは1行1リクエストなので、この3点は集計だけで絞れます。Claude Codeに任せる価値があるのは、集計コマンドを組み立てて結果を読み、次の絞り込みを提案させる部分です。

ここでは、ログをclaude -pに渡す方法を2通り示し、ステータス別・時間帯別・パス別・UA別の集計を順に回します。最後に、Claudeの読み取りをawkの数字で検算する手順を置きます。例のログは手順確認用に作った合成データ(1,218行)で、実際のサービスのものではありません。Nginxの設定そのものを書かせたい場合は、Claude Codeでnginx設定ファイルを作成する手順が別にあります。

5xxの切り分けは「時間帯 → パス → UA」の順に絞る

急増の原因は、たいていどれか1つの軸に偏って現れます。偏りを見つけるには、全体から細部へ向かう順序が速いです。

  1. ステータス別に数え、5xxが本当に増えているか、4xxが混ざっていないかを確かめる
  2. 5xxだけを時間帯で区切り、いつ始まって、いつ収まったかを見る
  3. 5xxだけをパスで区切り、特定のエンドポイントに偏っていないかを見る
  4. 5xxだけをUser-Agentで区切り、特定のクライアントやボットに偏っていないかを見る

どの軸も、前の軸で絞った行の中で数え直します。時間帯で11時台に絞れたなら、パスとUAは11時台の5xxだけで数えます。

前提: ログ形式と読ませ方

Nginxのaccess_logで形式を指定しなければ、定義済みのcombined形式が使われます。

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

空白で区切ると、$7がパス、$9がステータス、"で区切ると6番目のフィールドがUser-Agentになります。以降の集計はこの形式が前提です。log_formatを変えている環境では、列番号を自分の形式に合わせて読み替えます。合成ログの先頭は次のとおりです。

192.0.2.5 - - [27/Oct/2026:09:00:52 +0900] "GET /api/orders HTTP/1.1" 200 4589 "-" "curl/8.4.0"
192.0.2.33 - - [27/Oct/2026:09:00:13 +0900] "GET / HTTP/1.1" 200 507 "-" "curl/8.4.0"

ログを解析に渡す前に、IPアドレスは192.0.2.xのようなダミーに置き換えるか、集計に不要な列を落とします。ログには利用者のIPやURLのクエリ文字列が含まれるため、渡す範囲は集計に必要な列に絞るのが無難です。

方法1: 小さいログはパイプで渡す

claude -pは標準入力を読めるので、ログの一部をcatやtailでそのまま渡せます。公式のヘッドレスモードの説明にも、ビルドログをパイプで渡して原因を説明させる例があります。

tail -n 2000 /var/log/nginx/access.log \
  | claude -p '5xxが集中している時間帯とパスを挙げて'

この渡し方で気を付けるのは量です。公式の注記では、パイプで渡せる標準入力は10MBまでで、超えるとエラーメッセージとともに0以外の終了コードで終わります。大きい入力は、ファイルに書き出してパスをプロンプトで指定するよう案内されています。

10MBに収まるかは、ls -lhでファイルサイズを見れば分かります。アクセスの多いサイトでは、1日分のログがこれを超えることがあります。

方法2: 絞ってからファイルで読ませる

上限を超える分は、まず手元のコマンドで絞ります。Claudeに全行を読ませる必要はありません。5xxの急増を調べるなら、次の2段階で足ります。

# 1. 調べる時間帯の行だけを切り出す
grep '27/Oct/2026:1[01]:' /var/log/nginx/access.log > window.log
 
# 2. 5xxの行だけを別ファイルに残す
awk '$9 >= 500' window.log > errors-5xx.log
wc -l window.log errors-5xx.log

window.logは比較の分母(全リクエスト)、errors-5xx.logは原因探しの対象です。この2つのファイルのパスをプロンプトに書いて読ませます。

claude -p "window.log は調査時間帯の全リクエスト、errors-5xx.log はそのうち \
5xxだけです。5xxを時間帯(10分刻み)・パス・User-Agent別に集計し、\
どの軸に偏りがあるかを答えてください。数字の根拠になる \
コマンドも併記してください。" \
  --allowedTools "Read" "Bash(awk *)" "Bash(sort *)" "Bash(uniq *)"

--allowedToolsは、プロンプトを出さずに使わせるツールの許可リストです。公式の説明では、cat・head・tail・grep・wcなどは読み取り専用コマンドとして許可なしで動きます。awk・sort・uniqはその例に挙がっていないため、上の例ではBash(awk *)のように個別に許可しています。claude -pは、許可されていない承認が必要な操作を拒否するので、許可が足りないと集計が途中で止まります。

ログの置き場所によっては、ファイルをプロジェクトのディレクトリへ移してから読ませる必要があります。本番サーバーにclaudeを入れて実行するかどうかは、組織の運用ルールで決める部分です。手元にscpで必要な範囲だけ落として調べる形なら、権限を絞った状態で試せます。

集計を1つずつ回す

ここからは、合成ログで実際に各集計を回した結果を示します。Claudeに集計を頼むときも、このコマンドと同等のものを実行させ、出力を読ませます。

1. ステータス別 — 5xxが本当に増えたか

awk '{print $9}' access.log | sort | uniq -c | sort -rn
1155 200
  47 502
  16 404

1,218行のうち502が47件です。404も16件ありますが、これは5xxではないので原因探しの対象から外します。4xxと5xxを混ぜて「エラー率」を出すと、クライアント起因の404がサーバー障害を薄めます。

2. 時間帯別 — いつ起きたか

awk '$9>=500{split($4,a,":");print a[2]}' access.log | sort | uniq -c
  47 11

5xxは11時台にすべて集中しています。$4は[27/Oct/2026:11:23:45の形なので、:で分けた2番目が時です。分単位で見たければa[3]を足してa[2]":"a[3]で数えます。時間帯が決まったら、その時間の前後に何があったか(デプロイ・バッチ・設定変更)をログの外で確認します。

3. パス別 — どこで起きたか

awk '$9>=500{print $7}' access.log | sort | uniq -c | sort -rn
  47 /api/orders

全件が/api/ordersです。実サービスのログでは、クエリ文字列付きのURLが別々に数えられてしまうことがあります。その場合はsub(/\?.*/,"",$7)でクエリを落としてから数えます。

4. UA別 — 誰のリクエストか

UAは二重引用符で囲まれているため、-F'"'で区切って6番目を取ります。ステータスは3番目のフィールドの先頭にあるので、splitで取り出します。

awk -F'"' '{split($3,s," "); if (s[1]>=500) print $6}' access.log \
  | sort | uniq -c | sort -rn
  47 python-requests/2.31.0

5xxのUAはpython-requests/2.31.0だけです。ここで「このUAのクライアントが原因だ」と結論づけるのは早計です。このUAのリクエストの総数と、そのうち5xxになった割合を見なければ、「そのUAが壊した」のか「そのUAが問題のパスを叩く唯一のクライアントだった」のか区別できません。

パス × UAの総数と比べて、偏りの意味を決める

偏りは、分母と比べて初めて意味を持ちます。/api/ordersへの全リクエストのうち何割が5xxかを、UA別に見ます。

awk -F'"' '{split($2,r," "); split($3,s," ");
  if (r[2]=="/api/orders") {t[$6]++; if (s[1]>=500) e[$6]++}}
  END {for (u in t) printf "%s total=%d 5xx=%d\n", u, t[u], e[u]}' access.log

このコマンドは、/api/ordersへのリクエストをUA別に数え、そのうち5xxの件数を並べます。python-requestsの行で5xxの比率が高く、他のUAではほぼ0であれば、そのクライアントの送る内容や頻度に原因がある線が強まります。逆に、どのUAでも同じ割合で5xxが出ていれば、UAは原因ではなく、バックエンド側の問題と見るのが自然です。

ここまで絞れたら、Nginxのログだけでは見えない部分(アップストリームのエラーログ、アプリのログ)に移ります。$request_timeや$upstream_response_timeをログ形式に足してあれば、遅延が先に起きたかどうかもログから追えます。

Claudeの集計をawkの結果で検算する

LLMに数えさせると、行数が多いほど数え落としが起きやすくなります。数字そのものは、スクリプトの出力で確定させるのが安全です。検算は次の3点で足ります。

  1. 合計の一致: ステータス別の件数の合計がwc -lの行数と一致するか。上の例なら1,155+47+16=1,218です
  2. 部分と全体の一致: 時間帯別・パス別・UA別の5xx合計が、どれもステータス別の5xx件数(47)と一致するか。1つでも合わなければ、集計の前提(列番号、クエリ文字列、引用符の扱い)がずれています
  3. 別経路での再計算: Claudeが提示したコマンドを自分で実行して、同じ数字が出るか

3つ目のために、プロンプトで「数字の根拠になるコマンドも併記して」と求めておくと、検算が1コマンドで済みます。

awk '$9>=500' access.log | wc -l
47

合計が合わないときは、先にログの形を疑います。$9がステータスにならない行(引用符の中に空白を含むリクエスト行、壊れた行)が混ざっていることがあります。awk 'NF<9'で列数が足りない行を拾うと確認できます。

実行を自動化するときの注意

定期実行する場合は、スクリプトにclaude -pを組み込めます。公式の説明では、claude -pは成功時に終了コード0、失敗時に0以外で終わるので、シェルの分岐に使えます。CIや定期ジョブのように環境差を避けたい場面では、--bareを付けると、フックやCLAUDE.mdなどの自動読み込みを省いて起動します。ただし--bareではサブスクリプションのログインを使わず、ANTHROPIC_API_KEYなどの認証が別途要ります。

--output-format jsonを付けると、応答のJSONにtotal_cost_usd(クライアント側の推定値)が入ります。日次で回すと費用が積み上がるため、ログを集計コマンドで絞ってから渡す設計は、10MBの上限対策であると同時に費用対策にもなります。

誰にも承認できない無人実行では、承認が必要な操作は拒否されます。--allowedToolsで必要なコマンドだけを許可しておくか、--permission-prompts none(v2.1.259以降)で、プロンプトを待たずに拒否させる運用になります。

よくあるつまずき

ステータスの列がずれる: log_formatを独自に変えていると、$9がステータスになりません。先頭数行を見て列番号を決め、その番号をプロンプトに明記します。

時間帯がUTCとローカルでずれる: $time_localはサーバーのタイムゾーンで記録されます。+0900の表記を確認し、障害報告の時刻と比べるときはそろえます。

ボットや監視のUAが混ざる: ヘルスチェックやクローラーのUAは、通常でも一定数のリクエストを出します。5xxの偏りを見る前に、UA別の総数も見て、分母が極端に違うUAを区別します。

ログローテーションで期間が欠ける: access.log.1や.gzに分かれた期間をまたぐ調査では、zcatで結合してから絞ります。結合後のサイズが10MBを超えるなら、方法2のファイル経由に切り替えます。

数値データのログから異常の前兆を探す流れは、Claude CodeでPLCのログ解析でも同じ考え方で扱っています。受信ログを施設別に集計して原因候補を絞る例は、Claudeで機械警備の誤報を施設別に集計するにあります。

まとめ

5xxの急増は、ステータス別、時間帯別、パス別、UA別の順に絞れば、多くの場合は偏りの出る軸が見つかります。10MBを超えるログは、grepとawkで調査時間帯の5xxに絞ってから、ファイルのパスで読ませます。Claudeに任せるのは「どの軸で絞るか」の判断で、数字は必ずコマンドの出力で確定させます。合計の一致を見る習慣があれば、数え落としは検算の段階で見つかります。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. code.claude.com/docs/en/headless
  2. code.claude.com/docs/en/permissions
  3. nginx.org/en/docs/http/ngx_http_log_module.html
この記事を共有:XはてブLinkedIn