実体験:サーバーが突然「窒息」したとき
10以上の本番環境サーバークラスターを管理してきた6ヶ月間の「実戦」を経て、私はある致命的な脆弱性に気づきました。それは、ほとんどの中小規模のウェブサイトがHTTPの入り口を完全に無防備にしているということです。多くのエンジニアがコードの最適化に没頭する一方で、わずか5行のPythonスクリプトによるループ実行だけでNginxがハングアップしてしまうという事実を忘れています。
以前、ある「厄介な」ケースを対応しました。ECサイトの検索窓に対して、競合他社が毎秒50〜100リクエストのスパムを継続的に送り込んできたのです。このトラフィックは帯域幅を使い果たすほど(Volumetric DDoS)ではありませんでしたが、MySQLのCPU使用率を100%に跳ね上がらせました。その結果、正規の顧客が決済できなくなってしまったのです。ここで救世主となったのが**Limit Request Module**でした。
一般的な防御レイヤーの比較
設定に取り掛かる前に、自分がどの位置で防御しているのかを理解するために、各防御層を確認しておきましょう:
- Cloudflare (WAF/CDN): サーバーに到達する前に不要なトラフィックをフィルタリングする、非常に強力な最外層の防御です。ただし、オリジンIP(Origin IP)が漏洩している場合、この防御層は無意味になります。
- Firewall (iptables/nftables): ネットワーク層(レイヤー3/4)でブロックします。処理は非常に高速ですが、内容については「無知」です。ファイアウォールは、正規のユーザーとデータをスクレイピングしているボットを区別できません。
- Nginx Limit Request (レイヤー7): アプリケーション層における最終防衛線です。URL、IP、セッションを正確に把握します。これは、小規模な攻撃への対処や、過度なウェブスクレイピング(Web Scraping)を阻止するのに最適なツールです。
なぜリーキーバケット(Leaky Bucket)アルゴリズムは効果的なのか?
Nginxはトラフィックを制御するために**リーキーバケット**(穴の開いたバケツ)アルゴリズムを使用しています。リクエストをバケツに注がれる水、Nginxを底の小さな穴から一定の速度で水を流す処理系だと想像してください。水が急激に注がれてバケツから溢れた場合、余分なリクエストは即座に拒否されます。この仕組みにより、サーバーは安定したペースを維持し、トラフィック急増時の「熱暴走」を防ぐことができます。
詳細な実装ガイド
すべての設定は nginx.conf ファイル、または sites-available/ 内のサイト設定ファイルで行います。
ステップ1:共有メモリゾーン(Shared Memory Zone)の設定
Nginxは、どのIPがいくつリクエストを送信したかを記憶する場所を必要とします。http ブロックに以下の行を追加してください:
http {
# 10MBで約160,000個のIPステータスを保存可能
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;
}
パラメータの解説:
$binary_remote_addr: メモリを節約するためにIPをバイナリ形式で保存します(64ビットシステムでは、ステータスごとにわずか64バイトしか消費しません)。zone=mylimit:10m: 「mylimit」という名前の10MBのメモリ領域を作成します。rate=10r/s: IPごとに毎秒最大10リクエストのしきい値を設定します。
ステップ2:脆弱なセクションへの適用
サイト全体に盲目的に適用してはいけません。検索ページやログインページなど、最もリソースを消費する場所に集中させましょう。
server {
location /search/ {
# 20リクエストまでの「バースト」を許可し、即座に処理する
limit_req zone=mylimit burst=20 nodelay;
proxy_pass http://backend_app;
}
}
ステップ3:BurstとNodelayを深く理解する
ここは、正規のユーザーを誤ってブロックしてしまう原因になりやすい、最も混同しやすい部分です:
- Burst=20: チケット売り場の行列のようなものです。客が急に来た場合、すぐに追い返すのではなく、行列(最大20人)に並んで待ってもらうことができます。
- nodelay: このキーワードがない場合、Nginxは顧客を正確に10r/sの速度で待たせます(非常に遅く感じます)。
nodelayがあると、行列内の20リクエストは即座に処理されますが、21番目以降のリクエストはすぐにエラーを返します。
エラーコード429によるユーザー体験の最適化
デフォルトでは、Nginxは503(Service Unavailable)エラーを返します。しかし、Googlebotのような「クリーンな」ボットに対してより適切に通知するために、**429(Too Many Requests)**コードを使用することをお勧めします。
limit_req_status 429;
limit_req_log_level warn;
ログレベルを warn に設定することで、ディスク容量を急速に圧迫することなく、error.log ファイルを通じて不審なIPを簡単に追跡できます。
負荷耐性のテスト
実際に攻撃を受けるまで設定ミスに気づかない、という事態は避けましょう。ab (Apache Benchmark) ツールを使用して、100リクエストの集中アクセスをシミュレートできます:
ab -n 100 -c 10 http://yourdomain.com/search/
その後、ログを確認します:tail -f /var/log/nginx/error.log。もし「limiting requests…」というメッセージが表示されれば、システムは正しく保護されています。
実務から得た「痛い目を見ないための」教訓
- 締め付けすぎないこと: 現代のウェブサイトは、一度に30〜50個の静的ファイル(CSS、JS、画像)を読み込むことがあります。
burstなしでレートを低すぎ(例:2r/s)に設定すると、ウェブサイトのレイアウトが崩れてしまいます。 - 内部向けのホワイトリスト:
geoモジュールを使用して、オフィスのIPや監視サービス(UptimeRobot、Checklyなど)のIPを必ず除外してください。 - Nginx + Fail2Banのコンボ: Nginxはその時点でのリクエストをブロックするだけです。しつこいIPを永久に追放するには、Fail2Banを使用してNginxのログをスキャンし、そのIPをOSのファイアウォール(iptablesなど)に追加します。
DDoS対策は終わりのない軍拡競争です。しかし、Nginx Limit Requestをマスターすれば、現在の一般的な迷惑ボットの90%からサーバーを守ることができるでしょう。
