Web サイトのログに突然大量の 404 や 400 が出る場合、原因は通常ユーザーがリンクを押し間違えたことではなく、自動スキャナーが .env、.git、wp-admin、phpmyadmin、xmlrpc.php といったパスを探っていることが多いです。
この種のリクエストには、いくつか問題があります。
- access log が急速に大きくなる
- error log が意味の薄い記録で埋まる
- 静的サイトやリバースプロキシの接続が無効なリクエストに使われる
- 本当に見るべき問題がスキャンノイズに埋もれる
Nginx では limit_req と limit_conn を使って制限できます。ただし先に一点だけ重要です。Nginx は「レスポンスステータスが 404 または 400 だったら制限する」という処理を標準機能だけで直接行うことはできません。レート制限はレスポンス生成前に行われるためです。
実際には、404 / 400 を生みやすいスキャンパス、異常なアクセス元、サイト全体の高頻度アクセスを事前に制限します。
基本方針
まずは三層に分けるのがおすすめです。
- サイト全体にゆるいレート制限をかけ、単一 IP による高頻度アクセスを抑える。
- よくあるスキャンパスには厳しめの制限をかけ、直接
404を返す。 - IP ごとの同時接続数を制限する。
より安全な導入順は、まずスキャンパスのルールと access_log off を入れて 1 日観察することです。それでもランダムパスの 404 が多い場合に、サイト全体の limit_req を追加します。
まず http でレート制限用 zone を定義する
limit_req_zone と limit_conn_zone は必ず http {} の中に置きます。個別サイトの server {} には置けません。
/etc/nginx/nginx.conf の http {} に直接書けます。
|
|
別ファイルを作っても構いません。
|
|
内容は次の通りです。
|
|
前提として、nginx.conf の http {} 内で次が include されている必要があります。
|
|
次に server で zone を使う
サイト設定ファイルは通常 /etc/nginx/sites-enabled/www.example.com のような場所にあり、中身はたいてい server {} です。ここには limit_req_zone を書かず、前段で定義した zone だけを使います。
例:
|
|
サイト全体のレート制限で通常アクセスを巻き込むのが心配なら、最初はスキャンパスだけにしてもよいです。
|
|
各パラメータの意味
この行:
|
|
意味は次の通りです。
limit_req_zone:リクエストのレート制限に使うカウント用 zone を定義する。$binary_remote_addr:クライアント IP を制限キーにする。$remote_addrよりメモリ効率がよい。zone=perip_general:20m:perip_generalという名前の共有メモリ領域を20mのサイズで作る。rate=5r/s:IP ごとに平均で毎秒 5 リクエストまで許可する。
この行:
|
|
基本は同じですが、より厳しい設定です。
perip_scan:疑わしいスキャンパス専用。rate=1r/s:IP ごとに毎秒 1 リクエストだけ許可する。
この行:
|
|
意味は次の通りです。
limit_conn_zone:同時接続数制限に使うカウント用 zone を定義する。$binary_remote_addr:ここでもクライアント IP ごとに集計する。zone=addr_conn:20m:addr_connという名前の接続数カウント用共有メモリ領域を作る。
実際に同時接続数を制限するのは次です。
|
|
これは、各 IP の同時接続を最大 20 本にする、という意味です。
burst と nodelay の考え方
例えば:
|
|
次のように理解できます。
rate=5r/s:長期的な平均レートは毎秒 5 リクエスト。burst=30:短時間の突発分として 30 リクエストまで許容する。nodelay:平均レートを超えてもburst内なら待たせず処理し、burstを超えたら拒否する。
nodelay がない場合、Nginx は一部リクエストを遅延させてキューに入れようとします。通常の Web ページでは nodelay のほうが挙動を理解しやすいことが多いです。API や特に敏感なエンドポイントでは、実際の挙動に合わせて調整します。
よくあるエラー:limit_req_zone の配置場所が違う
次のようなエラーが出た場合:
|
|
これは、limit_req_zone を許可されていない場所に書いたという意味です。
よくある誤りは、server {} の中に置いてしまうことです。
|
|
これは動きません。
覚え方はシンプルです。
limit_req_zoneは「プールを定義する」ものなのでhttp {}に置く。limit_reqは「プールを使う」ものなのでserver {}またはlocation {}に置く。limit_conn_zoneは「接続プールを定義する」ものなのでhttp {}に置く。limit_connは「接続プールを使う」ものなのでserver {}またはlocation {}に置く。
明らかに異常な IP を一時的に拒否する
ログから、特定の IP が継続的にスキャンしていると確認できている場合は、一時的に拒否してもよいです。
|
|
この種の deny は server {} にも、特定の location {} にも置けます。長期的に残すかどうかは、誤検知リスクとアクセス元を見て判断します。
チェックしてリロードする
変更後はまず設定を確認します。
|
|
問題がなければリロードします。
|
|
いきなりサービスを再起動しないほうがよいです。reload なら Nginx が新しい設定を滑らかに読み込むため、リスクが低くなります。
推奨パラメータ
個人サイトや静的サイトなら、まずは次の値から始めるとよいです。
- 通常ページ:
rate=5r/sから10r/s - スキャンパス:
rate=1r/s - スキャンパスの
burst=5 - サイト全体の
burst=30 - IP ごとの同時接続:
10から20
通常ユーザーのアクセスが少ないサイトなら、さらに厳しくできます。画像、スクリプト、API リクエストが多いサイトでは、通常ページの制限を少し緩めて、本物のアクセスを巻き込まないようにします。
一番安全なのは段階的に入れる方法です。
- まずスキャンパスに
access_log off+return 404を入れる。 - 次に
perip_scanの厳しいレート制限を加える。 - 1 日ログを見る。
- ランダムパスの 404 がまだ多い場合、サイト全体のゆるいレート制限を有効にする。