深夜2時の電話と「真っ赤」なログ
深夜2時、テーブルの上でスマホが鳴り止まない。監視システムがサーバーCPUの95%急騰を検知して「悲鳴」を上げていた。WordPressとAPIを動かしているVPSで、日本国内のユーザー向けに運用しているサービスだ。これは明らかに異常事態。急いでSSHでaccess.logを確認すると、背筋が凍った。毎分数万件ものリクエストが押し寄せてくる。見知らぬIPアドレスの群れが、/wp-login.phpと/xmlrpc.phpに絶え間なくアタックを仕掛けていたのだ。
tail -f /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -nr | head -n 20
いくつかのIPを調べてみると、このプロジェクトとは無縁の地域からのアクセスばかりだった。国際的なボットネットによる大規模なブルートフォース攻撃だ。IPを一つひとつ手動でブロックするのは、砂浜で砂粒を数えるようなもの。そこで即座にGeoIPブロッキングの導入を決断した。目標はシンプルだ。対象ユーザーが存在しない国からのトラフィックを、まるごと遮断する。
代表的な3つのIPブロッキング手法
コマンドを打つ前に、よく使われる3つのアプローチを比較検討した。
1. ファイアウォール(UFW/IPTables)によるブロック
ネットワーク層でブロックするためサーバー負荷は最小限だ。しかし、IPTablesで国別に数万件ものIPレンジを管理するのは悪夢に等しい。データベース更新のたびにスクリプトが長時間走り、システムがフリーズするリスクもある。
2. CloudflareのGeoIP機能を利用
CloudflareをプロキシとしてすでにCDNに通しているなら、これが一番お手軽だ。SecurityタブでRuleを一つ作るだけで完了する。ただし、レイテンシの最適化が求められるシステムや、厳格な内部セキュリティポリシーがある場合には、全トラフィックをCloudflare経由にしたくないケースも多い。
3. NginxとGeoIP2モジュールの組み合わせ
パフォーマンスと柔軟性のバランスが最も優れた選択肢だ。NginxがMaxMindのバイナリデータベース(.mmdb)を直接読み込み、数ミリ秒以内に国を特定する。httpブロックやserverブロックでブロッキングを適用したり、ユーザーへのレスポンスをカスタマイズしたりと、非常に柔軟な運用が可能だ。さらにNginxにModSecurityを組み合わせたWAF構成にすれば、GeoIPと多層防御でより堅牢なシステムを構築できる。
MaxMind GeoLite2で十分な理由
MaxMindが無償提供するGeoLite2は、国レベルの精度が約99%だ。有償版のほうが精度は高いが、ボットネット対策やサーバー負荷軽減が目的であれば、無償版で十分すぎるほどの効果がある。
ちなみに、IPブロッキングと合わせて管理者パスワードも必ず強力なものに変更してほしい。おすすめはパスワードジェネレーター(toolcraft.app/ja/tools/security/password-generator)だ。このツールは完全にブラウザ上で動作するため、ネットワーク経由でパスワードが漏洩する心配がない。
実際の導入手順
ステップ1:GeoIP2モジュールのインストール
Ubuntu 22.04なら、コマンド一発でモジュールをインストールできる。
sudo apt update && sudo apt install libnginx-mod-http-geoip2 -y
インストール後、以下のコマンドでモジュールがNginxに組み込まれていることを確認しよう。
nginx -V 2>&1 | grep --color geoip2
ステップ2:MaxMindからデータベースを取得
現在、MaxMindはデータベースのダウンロードにアカウント登録が必要だ。登録後に「License Key」を発行し、geoipupdateツールを使えば毎週自動でデータを更新できる。
sudo apt install geoipupdate -y
/etc/GeoIP.confに取得したKeyの情報を設定し、geoipupdateコマンドを実行する。データベースファイルは/usr/share/GeoIP/GeoLite2-Country.mmdbに配置される。
ステップ3:Nginxに国の判定方法を教える
/etc/nginx/nginx.confを開き、http { ... }ブロック内に以下を追加する。
http {
geoip2 /usr/share/GeoIP/GeoLite2-Country.mmdb {
auto_reload 5m;
$geoip2_data_country_code default=XX source=$remote_addr country iso_code;
}
map $geoip2_data_country_code $allowed_country {
default yes;
CN no; # 中国をブロック
RU no; # ロシアをブロック
BR no; # ブラジルをブロック
}
}
この$allowed_country変数がフィルターとして機能する。ロシア(RU)や中国(CN)からのIPが来た場合、即座にnoというラベルが付与される。
ステップ4:ブロッキングを有効化する
次に、サイトの設定ファイルに適用しよう。
server {
listen 80;
server_name itfromzero.com;
if ($allowed_country = no) {
return 403;
}
location / {
# Webアプリのコードはここに記述
}
}
最後に構文チェックを行い、Nginxに設定を再読み込みさせる。
sudo nginx -t && sudo systemctl reload nginx
導入後の劇的な改善
Nginxをリロードした直後から、ログ上の迷惑リクエストが毎分10,000件から100件未満まで激減した。サーバーのCPU使用率も95%から安定した10%水準まで一気に下がった。GeoIPブロッキングと合わせてNginxのLimit Requestでレート制限を設けると、残った国内リクエストに対しても過負荷対策が二重にかかり、さらに安定した運用が実現できる。
ただし、運用上で忘れてはならない3つの注意点がある。
- Googlebotはブロックしない:SEOを重視するなら、Googleのクローラーは主にアメリカ(
US)から来るため、必ず許可リストに入れておこう。 - VPN経由は素通りする:GeoIPブロッキングは、日本国内のVPN出口ノードを使われると回避される。あくまでも国際ボットネットからの大量スパムトラフィックを工業的に除去するフィルターだと理解しておくこと。
- 定期更新のスケジュールを設定する:各国のIPレンジは頻繁に変更される。
geoipupdateを週1回実行するcronジョブを必ず設定しよう。
サーバーセキュリティは終わりのない戦いだ。GeoIPブロッキングは窓を閉めてホコリの侵入を防ぐようなものであり、玄関のドアにはしっかりとした鍵が必要なのは言うまでもない。Linuxサーバーのハードニングチェックリストも合わせて確認し、多層的な防御を整えておこう。ボットネットに夜中に叩き起こされない、穏やかな夜を過ごせることを願っている!

