2026年8月3日
2026年8月3日
NginxのGeoIPでアクセス地域を制限する(WordPress管理画面を国内限定にする設定例)
Nginx で国や地域ごとにアクセスを制限するには、MaxMind 形式(.mmdb)の IP 位置データベースと GeoIP2 モジュールを使い、判定した国コードを map で「許可/拒否」に変換して、必要な場所だけで 403 を返すのが現在の標準的なやり方です。この記事では、WordPress のログイン画面と管理画面を日本からのアクセスに限定する例を使って、設定の手順、固定 IP の例外、CDN 配下での注意点、検索エンジンや証明書更新を巻き込まないための除外設定までを説明します。
地域制限で防げること・防げないこと
最初に効果の範囲を押さえておきます。国別の制限は、海外から機械的に繰り返されるログイン試行やスパム投稿を大きく減らせる、手間の割に効果の高い対策です。一方で次のような限界があります。
- VPN やプロキシ、海外のクラウドを経由すれば国内からのアクセスに見せかけられるため、攻撃を止める仕組みではなく「数を減らす」仕組みです。
- IP アドレスと国の対応は変わり続けるので、データベースが古いと判定を誤ります。
- 出張先や海外旅行中の管理者も締め出されます。
そのため、パスワードの強化や二段階認証、ログイン試行回数の制限と組み合わせて使うことが前提になります。サイト全体を国内限定にするのは、後述する検索エンジンの問題もあるため、多くの場合おすすめできません。まずは wp-login.php と /wp-admin/ に絞るのが現実的です。
旧GeoIPモジュールではなくGeoIP2を使う
Nginx には標準で ngx_http_geoip_module という旧来のモジュールがありますが、これは旧形式(.dat)のデータベース用です。MaxMind は無料の旧形式データベースの配布をすでに終えているため、新しく構築するなら、.mmdb 形式を読める ngx_http_geoip2_module(サードパーティ製のモジュール)を使います。
Debian / Ubuntu では、このモジュールが libnginx-mod-http-geoip2 というパッケージで提供されています(2026年10月現在。パッケージ名や提供状況はディストリビューションの版によって異なる場合があります)。
sudo apt update
sudo apt install libnginx-mod-http-geoip2 mmdb-bin
パッケージで入れた場合、モジュールの読み込み設定は /etc/nginx/modules-enabled/ に自動で置かれます。ソースからビルドした場合は、nginx.conf の先頭に load_module の行を自分で書く必要があります。
データベースを用意して自動更新する
国の判定には、MaxMind の無料版である GeoLite2 Country がよく使われます。利用には MaxMind のサイトでのアカウント登録と、ライセンスキーの発行、利用規約への同意が必要です。登録はご自身で行い、発行されたアカウント ID とライセンスキーを geoipupdate の設定ファイルに書きます。
# /etc/GeoIP.conf(値はご自身のものに置き換える)
AccountID 000000
LicenseKey your-license-key
EditionIDs GeoLite2-Country
sudo apt install geoipupdate
sudo geoipupdate -v
ls -l /var/lib/GeoIP/
GeoLite2-Country.mmdb ができていれば準備完了です。データベースは定期的に更新されるので、cron や systemd タイマーで geoipupdate を週に1〜2回程度実行するようにしておきます。ライセンスキーは秘密情報なので、設定ファイルの権限を絞り、Git などで共有しないでください。
判定結果は mmdblookup で確かめられます。
mmdblookup --file /var/lib/GeoIP/GeoLite2-Country.mmdb --ip 203.0.113.10 country iso_code
DB-IP など、MaxMind 以外の事業者も .mmdb 形式の無料データベースを公開しています。ライセンスの条件(帰属表示の要否など)はそれぞれ異なるため、使う前に提供元の規約を確認してください。
Nginxの設定:国コードで判定し、管理画面だけ制限する
http コンテキスト(Debian 系なら /etc/nginx/conf.d/geoip2.conf など)に、データベースの読み込みと判定用の変数を定義します。
# /etc/nginx/conf.d/geoip2.conf
geoip2 /var/lib/GeoIP/GeoLite2-Country.mmdb {
auto_reload 60m;
$geoip2_country_code country iso_code;
}
# 国に関係なく許可したい固定IP(例)
geo $trusted_ip {
default 0;
203.0.113.10 1;
198.51.100.0/24 1;
}
# 「固定IPか」と「国コード」を組み合わせて拒否するか決める
map "$trusted_ip:$geoip2_country_code" $deny_admin {
default 1;
"~^1:" 0;
"~:JP$" 0;
}
この map では、固定 IP に一致すれば国に関係なく許可、国コードが JP なら許可、それ以外(判定できなかった場合も含む)は拒否としています。国コードが空になる IP を通すかどうかは方針次第ですが、ここでは安全側に倒して拒否にしています。
次に、WordPress の server ブロックで制限をかけます。PHP-FPM を使う一般的な構成の例です。
server {
# …既存の listen / server_name / root などはそのまま…
location = /wp-login.php {
if ($deny_admin) { return 403; }
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php-fpm.sock;
}
# admin-ajax.php は公開側の機能でも使われるので制限しない
location = /wp-admin/admin-ajax.php {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php-fpm.sock;
}
location ^~ /wp-admin/ {
if ($deny_admin) { return 403; }
try_files $uri $uri/ /index.php?$args;
location ~ \.php$ {
# 入れ子の location には外側の if が効かないので、ここにも書く
if ($deny_admin) { return 403; }
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php-fpm.sock;
}
}
}
fastcgi_pass のソケットのパスや snippets/fastcgi-php.conf の有無は環境によって異なります。既存の location ~ \.php$ の書き方に合わせて調整してください。Nginx の if は使い方によって予期しない動きをすることで知られていますが、return だけを書く使い方は公式にも安全とされている形です。また、/wp-admin/index.php のような PHP へのリクエストは入れ子の location ~ \.php$ で処理され、外側の location に書いた if は実行されません。入れ子の側にも同じ判定を書いているのはそのためで、ここを忘れると画像や CSS だけが拒否され、管理画面そのものは開けてしまいます。
admin-ajax.php を除外しているのは、フォームの送信や「いいね」ボタンなど、公開側の機能がこのファイルを呼ぶプラグインが多いためです。ここまで制限すると、海外からの訪問者がフォームを使えなくなります。
設定したら構文を確認して反映します。
sudo nginx -t && sudo systemctl reload nginx
CDNやロードバランサーの配下にある場合
GeoIP2 モジュールは、既定では $remote_addr(Nginx に直接つないできた相手の IP)で判定します。Cloudflare などの CDN やロードバランサーを前に置いていると、この値が CDN の IP になり、正しく判定できません。対処は2通りです。
- realip モジュールで元の IP に置き換える:
set_real_ip_fromで信頼するプロキシの IP 範囲を列挙し、real_ip_headerで元の IP が入ったヘッダーを指定します。信頼する範囲を広く取りすぎると、ヘッダーを偽装したアクセスで判定をすり抜けられるので、CDN 事業者が公開している IP 範囲だけに限定します。 - CDN が付ける国コードのヘッダーを使う: Cloudflare は国コードを
CF-IPCountryヘッダーで渡す機能を持っており、Nginx では$http_cf_ipcountryで参照できます。ただし、オリジンサーバーに CDN を経由せず直接アクセスできる状態だとヘッダーを偽装できるため、オリジン側で CDN 以外からの接続を拒否しておくことが前提です。CDN 側のファイアウォール機能で国別ルールを設定するほうが簡単な場合もあります。
制限してはいけないアクセスに注意する
地域制限で特に気をつけたいのは、海外から来る「正当な」アクセスです。
- 検索エンジンのクローラー: Googlebot は主に米国の IP からクロールします。サイト全体を国内限定にすると、検索結果から記事が消えるおそれがあります。
- 証明書の自動更新: Let's Encrypt の HTTP-01 検証は、複数の地点(国外を含む)からアクセスして確認する仕組みです。サイト全体に制限をかける場合は、
/.well-known/acme-challenge/を対象から外してください。 - 外部サービスからの通知: 決済サービスの Webhook、外部の死活監視、Jetpack などのサービス連携は、海外の IP から届くことがあります。
管理画面だけを制限する設定なら、これらの多くは影響を受けません。範囲を広げるときほど、除外するパスを丁寧に洗い出す必要があります。
動作を確認する方法
判定が意図どおりかを確かめるには、アクセスログに国コードを出すのが手軽です。
log_format geo '$remote_addr [$time_local] "$request" $status cc=$geoip2_country_code';
access_log /var/log/nginx/access_geo.log geo;
自分のアクセスが cc=JP と記録され、/wp-login.php が開けることを確認します。海外からの見え方は、海外リージョンの VPS やクラウドのインスタンスから curl -I https://example.com/wp-login.php を実行し、403 が返るかで確かめられます。
万一自分が締め出された場合は、SSH でサーバーに入り、geo ブロックに現在の IP を追加して reload すれば元に戻せます。設定を変える前には、既存の設定ファイルのバックアップを取っておいてください。
まとめ
- 新規構築では旧 GeoIP モジュールではなく、.mmdb 形式に対応した GeoIP2 モジュールを使います。
- GeoLite2 の利用にはアカウント登録とライセンスキーが必要で、
geoipupdateで定期的に更新します。 geoとmapを組み合わせると、固定 IP の例外を持たせたうえで国コードによる許可・拒否を判定できます。- 制限は
wp-login.phpと/wp-admin/に絞り、admin-ajax.php、クローラー、証明書の検証、外部サービスの通知を巻き込まないようにします。 - CDN 配下では realip の設定か CDN の国コードヘッダーを使い、ヘッダーの偽装に注意します。