手帳

  • 2025-06-21
  • IT系

Webサービスを運営していると、「特定の国からの不正アクセスが多い」「管理画面は日本からのアクセスのみに制限したい」といった課題に直面することがあります。

今回は、Docker + nginx-proxyの環境で、GeoIPを活用した効率的な国別アクセス制限を実装する方法をご紹介します。この方法なら、複数のWebサービスを一元管理しながら、柔軟なアクセス制御が可能になります。

なぜこの方法がおすすめなのか

従来の方法では、各サービスごとにGeoIP処理を実装する必要がありましたが、今回紹介する方法には以下のメリットがあります:

  • 一元管理: プロキシ側でGeoIP情報を取得し、各サービスに転送
  • パフォーマンス向上: 不正アクセスをプロキシレベルでブロック
  • 柔軟な制御: サービスごと、URLパターンごとに異なる制限を設定可能
  • 運用負荷軽減: 複数サービスでの重複実装が不要

システム構成

構成はシンプルです:

[インターネット] 
    ↓
[nginx-proxy (GeoIP module付き)]  ← IPアドレスから国情報を取得
    ↓ (X-GeoIP-Country-Codeヘッダーを付与)
[各Webサービス (nginx + PHP等)]   ← 国情報を利用してアクセス制御

主要コンポーネント:

  • nginx-proxy: GeoIPモジュール付きのリバースプロキシ
  • acme-companion: Let’s Encryptによる自動SSL証明書管理
  • 各Webサービス: WordPress、管理画面など

実装手順

Step 1: nginx-proxy側の基本設定

まず、GeoIP情報を取得してバックエンドに転送する設定を行います。

custom.conf

# 国別マッピング
map $geoip_country_code $allowed_country {
    default no;   # デフォルトは拒否
    JP yes;       # 日本からのアクセスを許可
}

# GeoIP情報をバックエンドに転送
proxy_set_header X-GeoIP-Country-Code $geoip_country_code;
proxy_set_header X-GeoIP-Country-Name $geoip_country_name;
proxy_set_header X-Real-IP $remote_addr;

Step 2: ドメイン別の制限設定

vhost.d/ディレクトリに各ドメイン用の設定ファイルを作成します。

パターン1: サイト全体を日本限定にする

# vhost.d/example.com
set $allowed 0;

# 日本からのアクセスを許可
if ($allowed_country = yes) {
    set $allowed 1;
}

# Let's Encrypt challenge pathは例外
if ($uri ~* "^/\.well-known/acme-challenge/") {
    set $allowed 1;
}

# 許可されていない場合は接続を切断
if ($allowed = 0) {
    return 444;  # 接続を即座に切断
}

パターン2: WordPressの管理画面のみ制限

これは実際の運用でよくあるパターンです。一般ユーザーは世界中からアクセス可能だが、管理画面は日本限定にしたい場合:

# vhost.d/wordpress-site.com
set $geo_allowed 1;  # デフォルトは許可
set $is_admin 0;

# 管理系URLの判定
if ($uri ~ "^/wp-login\.php") {
    set $is_admin 1;
}
if ($uri ~ "^/wp-admin/") {
    set $is_admin 1;
}

# 管理系URLへのアクセス時は一旦拒否
if ($is_admin = 1) {
    set $geo_allowed 0;
}

# 日本からの管理系URLアクセスは許可
set $check_country "${allowed_country}${is_admin}";
if ($check_country = "yes1") {
    set $geo_allowed 1;
}

# 許可されていない場合のみブロック
if ($geo_allowed = 0) {
    return 444;
}

Step 3: 各Webサービス側の設定

各サービスのnginxコンテナでは、プロキシから送られてくる情報を適切に処理します。

Dockerfile

#dockerfileFROM nginx:alpine
COPY custom-nginx.conf /etc/nginx/nginx.conf

custom-nginx.conf

user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    # GeoIP情報を含むカスタムログフォーマット
    log_format  geoip_combined '$remote_addr - $remote_user [$time_local] '
                               '"$request" $status $body_bytes_sent '
                               '"$http_referer" "$http_user_agent" '
                               'Country:$http_x_geoip_country_code';

    sendfile        on;
    keepalive_timeout  65;

    include /etc/nginx/conf.d/*.conf;
}

サーバー設定 (conf.d/default.conf)

server {
    listen 80;
    server_name _;
    root /var/www/html;
    index index.php index.html;

    # 実際のクライアントIP取得設定
    set_real_ip_from 172.18.0.0/16;  # Dockerネットワークの範囲
    real_ip_header X-Real-IP;
    real_ip_recursive on;

    # GeoIP情報の取得
    set $geoip_country_code $http_x_geoip_country_code;

    # 国情報付きのログ
    access_log /var/log/nginx/access.log geoip_combined;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ \.php$ {
        fastcgi_pass wordpress:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        
        # PHPに国情報を渡す
        fastcgi_param HTTP_X_GEOIP_COUNTRY_CODE $geoip_country_code;
        fastcgi_param REMOTE_ADDR $remote_addr;
    }
}

Step 4: PHPアプリケーションでの活用

WordPressなどのPHPアプリケーションでも、ヘッダー情報から国情報を活用できます:

// wp-config.php での例
if (!empty($_SERVER['HTTP_X_GEOIP_COUNTRY_CODE'])) {
    $country = $_SERVER['HTTP_X_GEOIP_COUNTRY_CODE'];
    $ip = $_SERVER['REMOTE_ADDR'];
    
    // 特定の国からのアクセスをブロック
    $blocked_countries = ['CN', 'RU', 'KP'];
    if (in_array($country, $blocked_countries)) {
        http_response_code(403);
        die('Access denied.');
    }
}

セキュリティのポイント

情報漏えいを防ぐ

地域制限していることを悟られないようにするのが重要です:

# ❌ 悪い例
return 403 "Access denied from your country";

# ⭕ 良い例
return 444;  # 接続を切断、情報を与えない

レスポンスコードの使い分け

  • 444: 接続を即座に切断(最も推奨)
  • 404: ページが存在しないふりをする
  • 403: アクセス拒否(明示的だが情報を与える)

Let’s Encryptへの配慮

SSL証明書の自動更新を妨げないよう、ACME challengeパスは必ず例外にしましょう:

if ($uri ~* "^/\.well-known/acme-challenge/") {
    set $allowed 1;
}

よくあるトラブルと解決法

ログにDockerの内部IPしか記録されない

原因: プロキシからの実IPが正しく取得できていない

解決法:

set_real_ip_from 172.18.0.0/16;
real_ip_header X-Real-IP;
real_ip_recursive on;

国コードが空になる

原因: nginx-proxyからのヘッダー転送設定漏れ

解決法:

proxy_set_header X-GeoIP-Country-Code $geoip_country_code;

ログフォーマットエラー

原因: log_formatの定義場所が間違っている

解決法: log_formatは必ずhttpブロック内で定義する

実際の運用効果

この仕組みを導入後、以下のような効果が得られました:

  • 不正アクセスの大幅削減: 特定地域からの攻撃的なアクセスを99%以上カット
  • サーバー負荷軽減: プロキシレベルでのブロックにより、バックエンドの処理負荷が軽減
  • 運用効率化: 複数サービスの一元管理により、設定変更が簡単に
  • 詳細な分析: 国別アクセス統計により、ユーザー動向の把握が容易に

まとめ

Docker環境でのGeoIPを使った国別アクセス制限は、セキュリティとパフォーマンス向上の両面で大きなメリットがあります。特に複数のWebサービスを運用している場合、この方法により統一的で効率的なアクセス制御が実現できます。

実装時のポイントは:

  • 正当なユーザーのアクセスを妨げないよう配慮する
  • Let’s Encryptの更新処理を考慮する
  • ログを定期的に確認し、必要に応じてルールを調整する

この実装により、より安全で効率的なWebサービス運用が可能になります。ぜひ試してみてください!