NginxでBotの大量アクセスを制限する方法(robots.txtの限界とrate limit対策)

WebサイトをBotの大量アクセスから守る方法を解説します。クローラーに「お願い」するだけのrobots.txtの限界を踏まえ、WebサーバーNginxのレートリミット機能を使ってアクセスを強制的に制限する設定を紹介。IPアドレス単位でリクエスト数を制限し、サーバー負荷を軽減する実践的な手法が学べます。

作成日: 更新日:

本番環境

  • OS: Amazon Linux 2023
  • nginx version: nginx/1.28.0

課題

Webサイトは、サーバーと呼ばれるコンピューター上で動作しています。ユーザーがWebサイトを閲覧するとき、このサーバーに対して「ページを見せてください」という要求が送られ、サーバーが応答することでページが表示されます。

通常、サーバーは多くのユーザーからの要求を同時に処理できます。しかし、その処理能力には限界があります。Bot(ボット)と呼ばれる自動化されたプログラムや、DDoS(ディードス)攻撃のように悪意を持って複数のコンピューターから一斉に行われる大量のアクセスが発生すると、サーバーの処理能力の限界を超えてしまいます。

このように、サーバーが処理しきれないほどの要求を受け取った状態を「サーバー負荷が高い」と言います。サーバー負荷が高くなると、以下のような問題が発生します。

  • レスポンスの遅延 サーバーが一つ一つの要求に応答するまでに時間がかかり、Webサイトの表示が極端に遅くなります。

  • サーバーダウン 最終的にサーバーが処理を継続できなくなり、完全に停止してしまう状態です。サーバーダウンすると、誰もWebサイトにアクセスできなくなります。

この課題は、このような悪意のある大量アクセスからWebサイトを守り、いつでも安定してサービスを提供できるようにするために解決する必要があります。

原因

「アクセス解析」とは、ウェブサイトやシステムに誰が、いつ、どのような経路で訪れたのかを記録し、分析する作業のことです。

このアクセス解析を行った結果、システムへの大量のアクセスの正体は、「Bot(ボット)」と呼ばれる自動化されたプログラムによるものであることが判明しました。Botは、人間では不可能な速さで機械的にアクセスを繰り返すことができます。

このようなBotからの異常な量のアクセスがサーバーに大きな負荷をかけ、サービスが遅くなったり、停止してしまったりする問題の原因となっていました。アクセスログ(アクセスの記録)を調べることで、特定のIPアドレスから短時間に集中してアクセスがあるなど、人間によるものとは思えない不自然な動きを検知し、それがBotによるものだと特定できます。

対策1: robots.txt の設置

Webサイトへの不要なアクセスを防ぐための基本的な対策として、「robots.txt」というファイルを設置する方法があります。このファイルは、Webサイトの情報を自動で収集するプログラム(クローラーやBotと呼ばれます)に対して、どのページにアクセスして良いか、またはしないで欲しいかを伝えるためのものです。

robots.txt
1# --- SEOスパム系 ---
2User-agent: DotBot
3Disallow: /
4
5User-agent: SemrushBot
6Disallow: /
7
8User-agent: AhrefsBot
9Disallow: /
10
11User-agent: MJ12bot
12Disallow: /
13
14User-agent: PetalBot
15Disallow: /
16
17# --- それ以外の全Bot ---
18User-agent: *
19Crawl-delay: 10
20Disallow:
21
22Sitemap: https://sample/sitemap.xml

このコードは、特定のクローラーに対してアクセスを禁止するルールを記述しています。

  • User-agent: 対象となるクローラーの名前を指定します。例えば DotBotSemrushBot が指定されています。
  • Disallow: /: / はサイトの全てのページを意味します。つまり、「指定したクローラーは、このサイトのどのページにもアクセスしないでください」という指示になります。
  • User-agent: *: * は「全てのクローラー」を意味する特別な記号です。
  • Crawl-delay: 10: 「アクセスする間隔を10秒空けてください」という指示で、サーバーへの負荷を軽減する目的で設定します。
  • Disallow:(値が空): この場合、「特にアクセスを禁止するページはありません」という意味になります。
  • Sitemap:: クローラーにサイトの構造を伝えるファイル(サイトマップ)の場所を教えています。

しかし、この方法はクローラーに対する「お願い」に過ぎず、法的な強制力はありません。ルールを守る一般的なクローラーには有効ですが、悪意のあるクローラーはrobots.txtの指示を無視してアクセスしてくることがあります。そのため、この対策だけでは不正なアクセスを完全に防ぐことはできず、根本的な解決にはなりません。

対策2: Nginxでのアクセス制限

Nginxは、Webサーバーとして機能するソフトウェアです。このNginxが持つアクセス制限機能を利用して、特定のIPアドレスから短時間に大量のアクセスが来ることを防ぐ設定を行います。IPアドレスとは、インターネットに接続された機器を識別するための番号で、インターネット上の住所のようなものです。

この設定により、決められた時間内に許可されるリクエスト数を超えたアクセスがあった場合、サーバーは「リクエストが多すぎます」という意味の「429 Too Many Requests」というエラーを返すようになります。これにより、悪意のあるプログラムなどからの攻撃を防ぎ、サーバーの負荷を軽減することができます。

まず、Nginxの全体的な設定ファイルである/etc/nginx/nginx.confに、アクセス制限のルールを定義します。

  • limit_req_zone $binary_remote_addr zone=botlimit:10m rate=3r/s;
    • これはアクセス制限ルールの定義です。
    • $binary_remote_addrは、アクセスしてきたユーザーのIPアドレスを示します。
    • zone=botlimit:10mは、「botlimit」という名前で10メガバイトのメモリ領域を確保し、そこに各IPアドレスからのアクセス状況を記録します。
    • rate=3r/sは、1つのIPアドレスあたり、1秒間に3リクエストまで許可するという制限レートを設定しています。
  • limit_req_status 429;
    • 上記のレート制限を超えたアクセスに対して、HTTPステータスコード429を返すように指定します。
/etc/nginx/nginx.conf
1http {
2    ...
3    # ==============================
4    # Bot / Rate Limit 追加
5    # ==============================
6    limit_req_zone $binary_remote_addr zone=botlimit:10m rate=3r/s;
7    limit_req_status 429;
8    ...
9
10    server {
11        ...
12    }
13}

次に、Webサイト個別の設定ファイルである/etc/nginx/conf.d/default.confで、先ほど定義したルールをどの場所に適用するかを指定します。

この例では、CSS、JavaScript、画像といった静的なファイルへのアクセスには制限をかけず、Webページの本体であるHTMLなどへのアクセスにのみ制限を適用しています。これは、Webページを表示する際に多数の静的ファイルが同時に読み込まれるため、それらの通信を妨げないようにするためです。

  • location / { ... }
    • このブロックは、静的ファイル以外のすべてのリクエストに対する設定を記述する場所です。
  • limit_req zone=botlimit burst=5;
    • nginx.confで作成したbotlimitという名前のルールを、この場所に適用します。
    • burst=5は、設定したレート(1秒あたり3リクエスト)を一時的に超えても、最大5リクエストまでは即座にエラーにせず、順番に処理するために待機させる設定です。これにより、Webサイトを普通に利用しているユーザーの操作が、意図せず制限に引っかかってしまうことを防ぎます。
/etc/nginx/conf.d/default.conf
1server {
2    # =========================
3    # 静的リソース
4    # =========================
5    location ~* \.(?:css|js|jpg|jpeg|png|gif|ico|svg|webp|woff|woff2|ttf|eot)$ {
6        proxy_pass http://127.0.0.1:3000;
7    }
8
9    # =========================
10    # HTML / SSR だけアクセス頻度の制限
11    # =========================
12    location / {
13        limit_req zone=botlimit burst=5;
14
15        ...
16    }
17    ...
18}

設定ファイルの編集が終わったら、記述内容に文法的な誤りがないかを確認します。以下のコマンドを実行してください。

構文チェック
1sudo nginx -t

コマンドの実行後、以下のように「syntax is ok」と「test is successful」というメッセージが表示されれば、設定ファイルは正常です。

構文チェック結果
1nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
2nginx: configuration file /etc/nginx/nginx.conf test is successful

最後に、以下のコマンドでNginxを再起動し、変更した設定を反映させます。reloadは、Webサーバーのサービスを停止させることなく、安全に設定を読み込ませるためのコマンドです。

nginx再起動
1sudo systemctl reload nginx

動作テスト

このシェルスクリプトは、指定したWebサイトに対して、短時間に多くのリクエストを同時に送信するテストを実行するものです。具体的には、20回のリクエストを並列で実行し、サーバーからの応答として返されるHTTPステータスコードを記録・表示します。

並列処理
1#!/bin/bash
2
3URL="https://sample.com/"
4TMPFILE=$(mktemp)
5
6# 20回並列でリクエストを送信
7for i in {1..20}; do
8  (
9    idx=$(printf "%02d" "$i")
10    code=$(curl -s -o /dev/null -w "%{http_code}" "$URL")
11    echo "$idx $code" >> "$TMPFILE"
12  ) &
13done
14
15# 全てのバックグラウンドプロセスの終了を待機
16wait
17
18# 番号順にソートして表示
19sort "$TMPFILE" | while read -r idx code; do
20  echo "request $idx -> $code"
21done
22
23# 一時ファイルの削除
24rm "$TMPFILE"
25echo "done"

スクリプトの解説

  1. URL="https://sample.com/" テスト対象のWebサイトのURLを変数に設定しています。
  2. TMPFILE=$(mktemp) 各リクエストの結果を一時的に保存するための、重複しない名前の一時ファイルを自動で作成しています。
  3. for i in {1..20}; do ... done ...の部分の処理を20回繰り返します。
  4. ( ... ) & ()で囲まれたコマンド群をバックグラウンドで実行します。末尾の&がその指示です。これにより、1つのリクエストの完了を待たずに次のリクエストを開始するため、20回のリクエストがほぼ同時に(並列で)実行されます。
  5. code=$(curl ...) curlコマンドを使って、指定されたURLにHTTPリクエストを送信し、-w "%{http_code}"オプションによってHTTPステータスコードだけを取得して、変数codeに保存しています。
  6. echo "$idx $code" >> "$TMPFILE" リクエスト番号と取得したHTTPステータスコードを、先ほど作成した一時ファイルに追記しています。
  7. wait バックグラウンドで実行されているすべてのリクエスト処理が完了するまで、ここで処理を待ち合わせます。
  8. sort "$TMPFILE" | ... 一時ファイルの内容を番号順に並べ替えてから、見やすい形式で表示しています。リクエストは並列で実行されるため、結果がファイルに書き込まれる順序は保証されません。そのためsortコマンドで整列させています。
  9. rm "$TMPFILE" 最後に、不要になった一時ファイルを削除しています。

実行結果の解説

以下は、上記のスクリプトを実行した結果の例です。

実行結果
1request 01 -> 200
2request 02 -> 200
3request 03 -> 200
4request 04 -> 200
5request 05 -> 200
6request 06 -> 200
7request 07 -> 429
8request 08 -> 429
9request 09 -> 429
10request 10 -> 429
11request 11 -> 429
12request 12 -> 429
13request 13 -> 429
14request 14 -> 429
15request 15 -> 429
16request 16 -> 429
17request 17 -> 429
18request 18 -> 429
19request 19 -> 429
20request 20 -> 200
21done

この結果から、各リクエストがサーバーからどのような応答を受け取ったかがわかります。

  • 200 このHTTPステータスコードは「成功」を意味します。リクエストはサーバーによって正常に処理されました。
  • 429 このHTTPステータスコードは「Too Many Requests」を意味します。これは、一定時間内にクライアントから送られたリクエストが多すぎたため、サーバーが一時的にリクエストの受け入れを拒否したことを示します。

このテスト結果から、このWebサーバーには、短時間に大量のアクセスが来た場合にそれを制限する仕組み(レートリミット)が導入されていることがわかります。初めの数回のリクエストは成功していますが、リクエストが集中したため、途中からサーバーに拒否され始め、最後のリクエストは制限が解除されたタイミングで成功したと推測できます。

その他検討事項

ここでは、Webサーバーへの過剰なアクセスを制限し、安定性を保つための設定について解説します。悪意のあるプログラム(ボット)などによる短時間での大量アクセスからサーバーを守るために重要な設定です。

サーバー全体での制限ルールの定義

まず、サーバー全体でどのような制限をかけるかのルールを定義します。この設定はNginxのメイン設定ファイルである nginx.conf に記述します。

/etc/nginx/nginx.conf
1    # ==============================
2    # Bot / Rate Limit 追加
3    # ==============================
4    limit_req_zone $binary_remote_addr zone=botlimit:10m rate=3r/s;
5    limit_conn_zone $binary_remote_addr zone=addr:10m;
6    limit_req_status 429;

このコードは、アクセス制限のための「ルール」を2つ定義しています。

  • limit_req_zone: アクセス「頻度」を制限するためのルールです。
    • $binary_remote_addr: アクセスしてきたユーザーをIPアドレスで識別します。
    • zone=botlimit:10m: botlimit という名前で、IPアドレスごとのアクセス情報を記録するためのメモリ領域を10メガバイト確保します。
    • rate=3r/s: 1つのIPアドレスから1秒間に許可するリクエスト数を3回までと定めています。
  • limit_conn_zone: 同時「接続数」を制限するためのルールです。
    • $binary_remote_addr: こちらもIPアドレスでユーザーを識別します。
    • zone=addr:10m: addr という名前で、同時接続数を記録するためのメモリ領域を10メガバイト確保します。
  • limit_req_status 429: 上記の制限を超えたアクセスがあった場合に、サーバーが返すHTTPステータスコードを「429 (Too Many Requests)」に設定します。これは「リクエストが多すぎます」という意味のエラーです。

特定の場所への制限ルールの適用

次に、先ほど定義したルールを、ウェブサイトのどの場所に適用するかを設定します。

/etc/nginx/conf.d/default.conf
1    # =========================
2    # HTML / SSR だけアクセス頻度の制限
3    # =========================
4    location / {
5        limit_req zone=botlimit burst=5;
6        limit_conn addr 5;
7
8        ...
9    }

このコードは、nginx.confで定義したルールを実際に適用する部分です。

  • location /: ウェブサイトのすべてのページ (/ 以下) にこの設定を適用するという意味です。
  • limit_req zone=botlimit burst=5;:
    • 先ほど定義したアクセス頻度制限のルール botlimit をここで適用します。
    • burst=5 は、一時的にリクエストが集中した場合でも、最大5リクエストまではキューに入れて処理を待たせることができる設定です。これにより、通常のユーザーがページを素早くクリックした際などに、すぐにエラーになるのを防ぎ、操作性を損なうことなくサーバーを保護できます。
  • limit_conn addr 5;:
    • 先ほど定義した同時接続数制限のルール addr を適用します。
    • これにより、1つのIPアドレスからの同時接続は5つまでに制限されます。

これらの設定を組み合わせることで、特定のユーザーからの過剰なアクセスを防ぎ、サーバーの負荷を軽減して安定したサービスを提供できるようになります。

おわりに

今回は、robots.txtによるお願いベースの対策では防げないBotの大量アクセスに対し、Nginxのlimit_req_zoneを使って強制的にアクセスを制限する方法を学びました。IPアドレスごとに秒間リクエスト数を制限することで、サーバーへの過剰な負荷を未然に防ぎ、サイトを安定稼働させることができます。このレートリミット設定は、Webサイトを安定して運用するための非常に効果的な対策です。

NginxでBotの大量アクセスを制限する方法(robots.txtの限界とrate limit対策) | いっしー@Webエンジニア