同じコードで、3回別のことが起きた
error.log を集計するようになって、AH コードごとに件数を数えられるようになりました。
すると AH01071 が何度も出てくる。同じコードなのだから同じ問題だろう——
と思っていたら、中身が毎回違いました。
# 1回目 [proxy_fcgi:error] AH01071: Got error 'Primary script unknown' # 2回目 [proxy_fcgi:error] AH01071: PHP message: PHP Warning: file_put_contents(...): Failed to open stream: Permission denied # 3回目 [proxy_fcgi:error] AH01071: PHP message: PHP Warning: Cannot use a scalar value as an array in ... on line 242
1回目はApache の設定の問題。2回目はファイルの権限。 3回目はプラグインのバグ。 直す場所も、直し方も、全部違います。
AH コードは、どこで振られるか
AH コードはモジュールごとに割り当てられた通し番号です。
core、proxy_fcgi、mod_dir、mod_ssl などが、
それぞれ自分の番号帯を持っています。
つまりコードが示すのは「どのモジュールが、どの種類の状況を報告したか」だけです。 原因そのものではありません。
proxy_fcgi が「FastCGI から何かエラーを受け取った」と言っているだけです。何を受け取ったかは書いていません。それが本文に続きます。だから同じコードで中身が3通りにも4通りにもなります。
ログ行の構造を見ると、はっきりします。
[時刻] [モジュール:レベル] [pid …] [client …] AHxxxxx: 本文 ~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~ ~~~~ どこが報告したか 見出し 中身
実際に見た8種類
自分のサーバーで観測したものを並べます。
| コード | モジュール | 中身 | 対応 |
|---|---|---|---|
| AH01071 | proxy_fcgi | Primary script unknown | 要対応 |
| AH01071 | proxy_fcgi | PHP Warning: 権限エラー | 要対応 |
| AH01071 | proxy_fcgi | PHP Warning: 型エラー | 要対応 |
| AH00124 | core | 内部リダイレクトの上限超過 | 要対応 |
| AH10244 | core | 不正な URI パスを拒否 | 対処不要 |
| AH01276 | dir | index が無く一覧も禁止 | 対処不要 |
| AH02032 | ssl | SNI なし接続で別 Host を要求 | 対処不要 |
| AH00489 | mpm_event | 起動・再開の記録 | 対処不要 |
注目すべきは半分が「対処不要」だということです。 Apache が正しく仕事をした記録が、エラーレベルで記録されている。
AH01071 は3つとも要対応ですが、
AH10244 と AH01276 と AH02032 は
何もしなくていい。
件数の多さで優先順位をつけると、対処不要なものに時間を使うことになります。
「対処不要」を、見分ける
いちばん役に立った判断の軸は、これでした。
拒否できているか
AH10244 は「不正な URI だったので受理しなかった」という記録です。
/cgi-bin/.%2e/.%2e/bin/sh のようなパストラバーサル探索が来て、
Apache がそれを弾いた。困っているのは攻撃者のほうです。
同じく AH01276 は 403 で、AH02032 は 421 で拒否しています。
いずれも応答を返し終えている。
自分の設定か、相手の行為か
| 状況 | 判断 |
|---|---|
| 壊れたリクエストが来た | 相手の行為。拒否できていれば不要 |
| 設定が矛盾している | 自分の問題。要対応 |
| アプリが例外を投げた | 自分の問題。要対応 |
| 起動・停止・再読み込み | 運用上の記録 |
AH00489(起動)や AH00169(停止)は
notice レベルですが、エラーを探しているときには目に入ります。
異常ではありません。
AH01071 の3回を、分ける
では同じコードの3つを、どう見分けるか。決め手は1つです。
PHP message:
これがあれば、PHP-FPM は動いています。 スクリプトが起動して、その中で警告や致命的エラーが出た。 それが Apache に転送されてきただけです。問題はアプリ側にあります。
無ければ、PHP-FPM に届く手前で失敗しています。
Primary script unknown は「そんなスクリプトは無い」という意味で、
存在しない .php を渡してしまっている。
実在チェックで防げる、Apache 側の問題です。
| 本文の形 | 動いた層 | 見る場所 |
|---|---|---|
Primary script unknown | FPM の手前 | Apache の受け渡し設定 |
PHP message: PHP Warning | PHP(起動済み) | アプリ・プラグイン |
PHP message: PHP Fatal error | PHP(起動済み) | 同上。より深刻 |
Cannot use a scalar value as an array)。
設定を詰めるより外すほうが早いと判断して削除したら、両方とも消えました。
ログ行を、切り分ける
実際に観測した8行です。選ぶと、3段階で切り分けが進みます。 上のトグルで「コードだけで判断」に切り替えると、何が起きるか見てください。
「コードだけで判断」にすると、AH01071 の3行がすべて同じ説明になります。 3つとも別の場所を直さなければならないのに、区別がつきません。 辞書を引く方式では、ここが限界です。
コードは、見出しにすぎない
AH01071 を3回見て、3回とも別の場所を直しました。
コードは「どのモジュールが報告したか」を示す見出しであって、
原因ではありませんでした。
そして半分は、直す必要すらなかった。
Apache が攻撃を弾いた記録や、起動の記録が、エラーを探す目には同じ「AH〜」として映ります。
これを見分けられるようになると、error.log はずっと静かになります。
この記事のまとめ
- AH コードはモジュールごとの通し番号。原因ではなく分類。
- 同じ
AH01071で、Apache の設定・ファイル権限・プラグインのバグと3通りあった。 - 決め手は
PHP message:の有無。あれば PHP は動いている=アプリ側の問題。 - 実測8種類のうち半分は対処不要。拒否できた記録や、起動の記録。
- 判断の軸は「サーバーが困っているのか、相手を断ったのか」。
- 件数の多さで優先順位をつけると、対処不要なものに時間を使う。
- 検索するなら、コードではなくメッセージ本文の特徴的な部分で。他人の AH01071 と自分の AH01071 は別物かもしれない。