Webサイトのセキュリティ確保や運用リスク低減のため、nginxによるアクセス制限は重要です。例えば「特定の管理画面だけ社内IPからアクセスしたい」「海外からの不審なリクエストをブロックしたい」など、アクセスルールの細分化は運用現場でも頻繁に登場する悩みのひとつです。また、クラウド化やリバースプロキシ(ELB/ALBなど)の導入によって状況も複雑化し、単純なIP制限だけでは不十分になるケースも増えています。
このように、nginxのアクセス制限には「allow/denyディレクティブ」「geoモジュール」「locationごとの個別設定」「X-Forwarded-For対応」など様々な手法や組み合わせが欠かせません。そして、実際に設定を反映した際に「なぜか意図通り動かない」「403 Forbiddenになってしまう」といったトラブルも多く、仕組みや正しい記述方法、運用のベストプラクティスを事前に理解しておくことが重要です。
この記事では、「即使える具体例」「現場で役立つTips」「クラウド・Docker環境での対処法」も交えながら、nginxのアクセス制限について解説します。
IP・地域・ホスト名によるアクセス制限の基本と応用

Nginxのアクセス制限機能は、Webサーバーのセキュリティを確保するための最も基本的かつ強力な手段です。特にIPアドレスベースの制限は、不正アクセスやサイバー攻撃からシステムを守る第一の防衛線として機能します。
本セクションでは、ngx_http_access_moduleを使ったIPアドレス制御の基本から、地域(国)単位での高度な制限方法まで、実務で即活用できる設定例を解説します。これらの設定は、管理画面の保護、開発環境へのアクセス制御、DDoS攻撃の予防など、多様なセキュリティ要件に対応できます。
特定IPアドレスを許可・拒否するallow/deny設定
Nginxのアクセス制限で最も基本となるのがallowとdenyディレクティブです。これらは記述された順序で上から評価され、最初にマッチした規則が適用されます。
ホワイトリスト方式(特定IPのみ許可)
最もセキュアな方式として、信頼できるIPアドレスのみを許可し、それ以外をすべて拒否するホワイトリスト方式があります。
# 特定のIPv4アドレスのみ許可
location /admin/ {
    allow 203.0.113.10;      # 自社オフィスIP
    allow 198.51.100.25;     # VPN接続IP
    deny all;                # その他すべて拒否
}
IPv6対応版:
location /admin/ {
    allow 203.0.113.10;                          # IPv4アドレス
    allow 2001:0db8:85a3::8a2e:0370:7334;       # IPv6アドレス
    deny all;
}
重要なポイント
- deny all;は必ず最後に記述します
- IPv4とIPv6は混在して記述可能です
- allow/- denyは記述順に評価されるため、順序が極めて重要です
ブラックリスト方式(特定IPを拒否)
特定の攻撃元IPや悪意あるボットのIPアドレスのみをブロックする場合:
location / {
    deny 192.0.2.50;         # 攻撃元として確認されたIP
    deny 192.0.2.100;        # 不正アクセスIP
    allow all;               # その他すべて許可
}
注意: ブラックリスト方式はセキュリティレベルが低いため、機密性の高いコンテンツ(管理画面等)にはホワイトリスト方式を推奨します。
複数IPアドレスをまとめて制限する方法とCIDR表記の使い方
複数のIPアドレスを個別に記述するのは非効率です。CIDR(Classless Inter-Domain Routing)表記を使うことで、IPアドレス範囲をまとめて指定できます。
CIDR表記の基本
CIDR表記はIPアドレス/プレフィックス長の形式で記述します:
CIDR表記例
- 192.168.1.0/24→ 192.168.1.0~192.168.1.255(256個のアドレス)
- 10.0.0.0/8→ 10.0.0.0~10.255.255.255(約1677万個のアドレス)
- 172.16.0.0/12→ 172.16.0.0~172.31.255.255(約104万個のアドレス)
実践的な設定例
# 社内ネットワーク全体を許可
location /internal/ {
    allow 192.168.1.0/24;    # 本社ネットワーク(256アドレス)
    allow 10.0.0.0/8;        # VPNネットワーク全体
    allow 172.16.0.0/16;     # 支社ネットワーク(65536アドレス)
    deny all;
}
IPv6のCIDR表記
IPv6でも同様にCIDR表記が使用できます:
location /api/ {
    allow 2001:db8::/32;                    # IPv6ネットワーク範囲
    allow 2001:0db8:85a3::/48;             # より狭い範囲
    allow 192.168.1.0/24;                   # IPv4も併記可能
    deny all;
}
活用ポイント:
- クラウドサービスのIPレンジ(AWS、GCPなど)を許可する際にCIDRが有効
- /32(IPv4)や- /128(IPv6)は単一IPと同義
- プレフィックス長が小さいほど、範囲が広くなる点に注意
海外IPをブロックして日本国内のみ許可するgeoモジュールの設定例
日本国内向けのサービスで海外からの不正アクセスを防ぎたい場合、ngx_http_geo_moduleとGeoIPデータベースを組み合わせることで、国単位のアクセス制御が実現できます。
GeoIP2モジュールのインストール
まず、MaxMind GeoIP2データベースとNginxモジュールを導入します:
# Ubuntu/Debianの場合
sudo apt update
sudo apt install libnginx-mod-http-geoip2
# CentOS/RHELの場合
sudo yum install nginx-module-geoip2
# MaxMind GeoLite2データベースのダウンロード(無料版)
# <https://dev.maxmind.com/geoip/geolite2-free-geolocation-data>
# アカウント登録後、GeoLite2-Country.mmdbをダウンロード
nginx.confでのGeoIP2設定
# httpコンテキスト内に記述
http {
    # GeoIP2データベースの読み込み
    geoip2 /usr/share/GeoIP/GeoLite2-Country.mmdb {
        auto_reload 5m;
        $geoip2_metadata_country_build metadata build_epoch;
        $geoip2_data_country_code country iso_code;
        $geoip2_data_country_name country names en;
    }
    # 国コードに基づくマッピング
    map $geoip2_data_country_code $allowed_country {
        default no;
        JP yes;    # 日本のみ許可
    }
    server {
        listen 80;
        server_name example.com;
        # 日本以外からのアクセスを拒否
        if ($allowed_country = no) {
            return 403;
        }
        location / {
            root /var/www/html;
            index index.html;
        }
    }
}
より柔軟な国別制限(複数国許可)
http {
    geoip2 /usr/share/GeoIP/GeoLite2-Country.mmdb {
        $geoip2_data_country_code country iso_code;
    }
    # 複数国を許可する例
    map $geoip2_data_country_code $allowed_country {
        default no;
        JP yes;    # 日本
        US yes;    # アメリカ
        GB yes;    # イギリス
        DE yes;    # ドイツ
    }
    server {
        listen 80;
        server_name example.com;
        # 管理画面は日本のみ、サイト本体は複数国許可
        location /admin/ {
            if ($geoip2_data_country_code != "JP") {
                return 403;
            }
            # 管理画面の設定...
        }
        location / {
            if ($allowed_country = no) {
                return 403 "Access denied from your country";
            }
            root /var/www/html;
        }
    }
}
特定の国からのアクセスのみブロック(ブラックリスト方式)
map $geoip2_data_country_code $blocked_country {
    default no;
    CN yes;    # 中国
    RU yes;    # ロシア
    KP yes;    # 北朝鮮
}
server {
    if ($blocked_country = yes) {
        return 403;
    }
}
注意事項
- GeoIPデータベースの更新: 
 MaxMindのGeoLite2データベースは定期的に更新されます。auto_reloadディレクティブで自動更新を有効化するか、cronで定期的にダウンロードしましょう
- VPN・プロキシ経由のアクセス: 
 VPNやプロキシを使用すると国判定が回避される可能性があります
- 誤検知のリスク: 
 IPアドレスの国判定は100%正確ではないため、重要な制限には他の認証手段との併用を推奨します
- パフォーマンス:
 GeoIPルックアップは若干のオーバーヘッドがあります。高トラフィックサイトではfastcgi_cacheなどとの併用を検討してください
- if文使用時の注意:
 Nginxの- ifディレクティブは「邪悪(evil)」とされる場合があります。可能であれば- mapディレクティブと組み合わせ、- locationブロック内での変数評価を推奨します。
これらの設定により、IPアドレス単位から国単位まで、様々な粒度でのアクセス制限が実現できます。次のセクションでは、特定のURLパスやディレクトリ単位でのより細かな制御方法を解説します。
特定のURLパスやディレクトリ単位でのアクセス制限の実践例

サイト全体ではなく、管理画面や機密ファイルなど特定のコンテンツのみを保護したい場合、locationディレクティブを使ったパス単位のアクセス制限が効果的です。このセクションでは、実務で頻繁に使われる具体的な制限パターンと、設定時の落とし穴を回避するベストプラクティスを解説します。
/adminや/wp-login.phpなど管理画面へのアクセス制限設定方法
管理画面は攻撃者の主要なターゲットです。IP制限により、信頼できるネットワークからのみアクセス可能にすることで、ブルートフォース攻撃やゼロデイ攻撃のリスクを大幅に軽減できます。
基本的な管理画面の保護
server {
    listen 80;
    server_name example.com;
    root /var/www/html;
    # 通常のコンテンツは制限なし
    location / {
        try_files $uri $uri/ =404;
    }
    # /admin/ディレクトリ全体を保護
    location /admin/ {
        allow 203.0.113.10;      # オフィスIP
        allow 192.168.1.0/24;    # 社内ネットワーク
        deny all;                # その他すべて拒否
        try_files $uri $uri/ /admin/index.php?$query_string;
    }
}
WordPress管理画面の保護
WordPressは特に攻撃対象になりやすいため、複数のエンドポイントを保護します:
server {
    listen 80;
    server_name wordpress-site.com;
    root /var/www/wordpress;
    index index.php;
    # wp-login.phpへのアクセス制限
    location = /wp-login.php {
        allow 203.0.113.10;      # 自社IP
        allow 198.51.100.0/24;   # VPNネットワーク
        deny all;
        include fastcgi_params;
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
    # /wp-admin/ディレクトリ全体を保護
    location ^~ /wp-admin/ {
        allow 203.0.113.10;
        allow 198.51.100.0/24;
        deny all;
        location ~ \\.php$ {
            include fastcgi_params;
            fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        }
    }
    # admin-ajax.phpは例外(AJAX通信で使用されるため)
    location = /wp-admin/admin-ajax.php {
        # IP制限なし(フロントエンドからのAJAX呼び出しを許可)
        include fastcgi_params;
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
    # その他の.phpファイル
    location ~ \\.php$ {
        include fastcgi_params;
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}
locationマッチングの優先順位
Nginxのlocationディレクティブには優先順位があり、理解していないと意図しない動作になります:
優先順位(高い順):
- location = /exact/path– 完全一致(最優先)
- location ^~ /prefix/– 前方一致(正規表現より優先)
- location ~ /regex/– 正規表現(大文字小文字区別あり)
- location ~* /regex/– 正規表現(大文字小文字区別なし)
- location /prefix/– 前方一致(正規表現に負ける)
server {
    # 例1: /admin/test.phpにアクセスした場合
    location = /admin/test.php {
        # ここが最優先で評価される(完全一致)
        allow 192.168.1.10;
        deny all;
    }
    location ^~ /admin/ {
        # 完全一致がない場合、ここが評価される
        allow 192.168.1.0/24;
        deny all;
    }
    location ~ \\.php$ {
        # ^~で先にマッチしているため、ここは評価されない
        # FastCGI設定...
    }
}
実践的な優先順位活用例:
# PHPファイル全般は通常処理、特定の管理用PHPのみ制限
location = /config.php {
    # 完全一致で最優先評価
    deny all;  # 直接アクセス完全禁止
}
location ^~ /admin/ {
    # /admin/配下すべてに制限
    allow 192.168.1.0/24;
    deny all;
    # 内部でPHP処理が必要な場合
    location ~ \\.php$ {
        include fastcgi_params;
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}
location ~ \\.php$ {
    # その他のPHPファイルは通常処理
    include fastcgi_params;
    fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
ファイル単位(例:.env、config.php)へのアクセス制御
機密情報を含む設定ファイルへの直接アクセスは、深刻なセキュリティリスクです。Nginxレベルで完全にブロックすることが必須です。
.envファイルの保護(Laravel、Node.js等)
server {
    listen 80;
    server_name example.com;
    root /var/www/html;
    # .envファイルへの直接アクセスを完全拒否
    location ~ /\\.env$ {
        deny all;
        return 404;  # 存在を隠すため404を返す
    }
    # より広範囲:すべての隠しファイルをブロック
    location ~ /\\. {
        deny all;
        access_log off;
        log_not_found off;
        return 404;
    }
}
複数の機密ファイルを正規表現でまとめて保護
server {
    listen 80;
    server_name example.com;
    root /var/www/html;
    # 機密ファイルのパターンマッチング
    location ~* \\.(env|git|svn|htaccess|htpasswd|ini|log|sh|sql|conf|bak|backup|swp|dist)$ {
        deny all;
        return 404;
    }
    # 特定のファイル名を完全一致で拒否
    location ~ ^/(config\\.php|database\\.php|\\.env\\.local|composer\\.(json|lock))$ {
        deny all;
        return 404;
    }
    # WordPressの機密ファイル
    location ~ ^/(wp-config\\.php|wp-config-sample\\.php|readme\\.html|license\\.txt)$ {
        deny all;
        return 404;
    }
}
データベースバックアップファイルの保護
# /backups/ディレクトリへの直接アクセス禁止
location ^~ /backups/ {
    deny all;
    return 404;
}
# .sqlや.sql.gzファイルへの直接アクセス禁止
location ~* \\.(sql|sql\\.gz|sql\\.zip|sql\\.bz2)$ {
    deny all;
    return 404;
}
PHP実行を防ぐ(アップロードディレクトリ等)
# /uploads/ディレクトリ内でのPHP実行を防止
location ^~ /uploads/ {
    location ~ \\.php$ {
        deny all;
        return 404;
    }
}
# より厳格:実行可能ファイルすべてを拒否
location ^~ /wp-content/uploads/ {
    location ~* \\.(php|php3|php4|php5|phtml|pl|py|jsp|asp|sh|cgi)$ {
        deny all;
        return 404;
    }
}
locationディレクティブ内での設定位置と組み合わせ制御のベストプラクティス
複数のセキュリティ機構を組み合わせる際、設定の記述順序と構造が重要になります。
allow/denyの評価順序
allowとdenyは記述された順序で上から評価され、最初にマッチした規則が適用されます:
# 正しい例:特定IPを拒否し、その他のローカルネットワークは許可
location /admin/ {
    deny 192.168.1.50;       # 問題のあるIPを先に拒否
    allow 192.168.1.0/24;    # その後ネットワーク全体を許可
    deny all;                # 最後にすべて拒否
}
# 間違った例:意図通り動作しない
location /admin/ {
    allow 192.168.1.0/24;    # 先に評価されるため
    deny 192.168.1.50;       # この行は実行されない(192.168.1.50は上でマッチ済み)
    deny all;
}
Basic認証とIP制限の組み合わせ
IP制限とBasic認証を併用することで、二重のセキュリティが実現できます:
# パターン1: IP制限とBasic認証の両方を要求(AND条件)
location /admin/ {
    # まずIP制限
    allow 192.168.1.0/24;
    deny all;
    # 次にBasic認証
    auth_basic "Restricted Admin Area";
    auth_basic_user_file /etc/nginx/.htpasswd;
    try_files $uri $uri/ /admin/index.php?$query_string;
}
# パターン2: IP制限またはBasic認証のいずれか(OR条件)
# satisfyディレクティブを使用
location /admin/ {
    satisfy any;  # いずれか1つの条件を満たせばOK(デフォルトはall)
    # IP制限
    allow 192.168.1.0/24;
    deny all;
    # Basic認証
    auth_basic "Admin Area - IP or Password";
    auth_basic_user_file /etc/nginx/.htpasswd;
}
satisfyディレクティブの挙動:
- satisfy all;(デフォルト):すべての条件(IP制限、Basic認証等)を満たす必要がある
- satisfy any;:いずれか1つの条件を満たせばアクセス可能
複雑な条件分岐が必要な場合のmap活用
複数の条件を組み合わせる場合、mapディレクティブで変数を作成すると可読性が向上します:
http {
    # IP範囲を変数にマッピング
    geo $internal_network {
        default 0;
        192.168.1.0/24 1;
        10.0.0.0/8 1;
    }
    # 時間帯を変数にマッピング(営業時間のみアクセス許可など)
    map $time_iso8601 $business_hours {
        default 0;
        ~T(09|1[0-7]):[0-5][0-9]:[0-5][0-9] 1;  # 09:00-17:59
    }
    server {
        location /admin/ {
            # 社内ネットワークかつ営業時間のみ許可
            if ($internal_network = 0) {
                return 403 "Access denied: External network";
            }
            if ($business_hours = 0) {
                return 403 "Access denied: Outside business hours";
            }
            try_files $uri $uri/ /admin/index.php?$query_string;
        }
    }
}
ネストされたlocationブロックの注意点
location ^~ /admin/ {
    # 外側のlocation:ディレクトリ全体に適用
    allow 192.168.1.0/24;
    deny all;
    # 内側のlocation:PHPファイルのみに適用
    location ~ \\.php$ {
        # 注意:外側のallow/denyは継承される
        # 追加の制限も可能
        allow 192.168.1.10;  # さらに特定IPに限定
        deny all;
        include fastcgi_params;
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}
重要な継承ルール
- 内側のlocationブロックでallow/denyを記述すると、外側の設定は上書きされる
- 継承させたい場合は、内側のブロックで再度記述する必要がある
設定の安全な適用手順
設定変更時は必ず以下の手順を踏むことで、サービス停止を防げます:
# 1. 設定ファイルの構文チェック
sudo nginx -t
# 2. エラーがなければリロード(ダウンタイムなし)
sudo nginx -s reload
# 3. 設定に問題がある場合の出力例
nginx: [emerg] "allow" directive is not allowed here in /etc/nginx/sites-enabled/default:15
nginx: configuration file /etc/nginx/nginx.conf test failed
# 4. エラーが出た場合は設定を修正し、再度nginx -tで確認
ベストプラクティス
- 本番環境で設定変更する前に、開発環境でテストする
- 設定ファイルはバージョン管理(Git等)で管理する
- allow/- denyを追加する際は、必ず自分のIPを先に- allowに追加してから作業する(ロックアウト防止)
- 重要な変更の前にバックアップを取得する
# 設定ファイルのバックアップ
sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.backup.$(date +%Y%m%d)
これらの実践例を活用することで、管理画面や機密ファイルを効果的に保護できます。次のセクションでは、リバースプロキシやクラウド環境での複雑なアクセス制限設定について解説します。
リバースプロキシ・クラウド環境での制限設定とトラブル対策

リバースプロキシやロードバランサーを経由する環境では、Nginxが直接受け取るIPアドレスはプロキシのIPとなり、実際のクライアントIPではありません。このセクションでは、AWS ELB/ALB、Cloudflare、Docker環境などで正しくアクセス制限を機能させるための設定方法と、よくあるトラブルの解決策を解説します。

X-Forwarded-Forヘッダーを考慮したクライアントIP制限方法
リバースプロキシ環境では、クライアントの実IPアドレスはX-Forwarded-For(XFF)ヘッダーに格納されます。Nginxでこれを正しく処理するには、ngx_http_realip_moduleの設定が必要です。
基本的なrealipモジュールの設定
http {
    # リバースプロキシのIPアドレスを信頼する
    set_real_ip_from 10.0.1.0/24;        # プロキシサーバーのIPレンジ
    set_real_ip_from 172.31.0.0/16;      # AWSのプライベートIPレンジ
    # クライアントIPの取得元ヘッダーを指定
    real_ip_header X-Forwarded-For;
    # 信頼できるプロキシのIPを再帰的にスキップ
    real_ip_recursive on;
    server {
        listen 80;
        server_name example.com;
        # これで$remote_addrに実際のクライアントIPが入る
        location /admin/ {
            allow 203.0.113.10;      # 実際のクライアントIPで制限可能
            deny all;
            # デバッグ用ログ
            access_log /var/log/nginx/admin_access.log combined;
        }
    }
}
AWS ELB/ALB環境での完全な設定例
AWS Elastic Load Balancerを使用する場合の推奨設定:
http {
    # AWSのロードバランサーIPレンジを信頼
    # VPCのCIDRに合わせて調整してください
    set_real_ip_from 10.0.0.0/8;         # VPCプライベートIP
    set_real_ip_from 172.16.0.0/12;      # 追加のプライベートレンジ
    # ALBが付与するヘッダーからIPを取得
    real_ip_header X-Forwarded-For;
    # 再帰的な処理を有効化(複数プロキシ対応)
    real_ip_recursive on;
    # ログフォーマットにクライアントIPを含める
    log_format main_with_real_ip '$remote_addr - $remote_user [$time_local] '
                                  '"$request" $status $body_bytes_sent '
                                  '"$http_referer" "$http_user_agent" '
                                  '"$http_x_forwarded_for" real_ip=$realip_remote_addr';
    server {
        listen 80;
        server_name example.com;
        access_log /var/log/nginx/access.log main_with_real_ip;
        location /admin/ {
            # 実際のクライアントIPで制限
            allow 203.0.113.10;
            allow 198.51.100.0/24;
            deny all;
            proxy_pass <http://backend>;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }
}
Cloudflare経由の場合の設定
Cloudflareをリバースプロキシとして使用する場合、専用ヘッダーCF-Connecting-IPを使用します:
http {
    # Cloudflareの全IPレンジを信頼
    # 最新リストは <https://www.cloudflare.com/ips/> から取得
    set_real_ip_from 173.245.48.0/20;
    set_real_ip_from 103.21.244.0/22;
    set_real_ip_from 103.22.200.0/22;
    set_real_ip_from 103.31.4.0/22;
    set_real_ip_from 141.101.64.0/18;
    set_real_ip_from 108.162.192.0/18;
    set_real_ip_from 190.93.240.0/20;
    set_real_ip_from 188.114.96.0/20;
    set_real_ip_from 197.234.240.0/22;
    set_real_ip_from 198.41.128.0/17;
    set_real_ip_from 162.158.0.0/15;
    set_real_ip_from 104.16.0.0/13;
    set_real_ip_from 104.24.0.0/14;
    set_real_ip_from 172.64.0.0/13;
    set_real_ip_from 131.0.72.0/22;
    # IPv6レンジ
    set_real_ip_from 2400:cb00::/32;
    set_real_ip_from 2606:4700::/32;
    set_real_ip_from 2803:f800::/32;
    set_real_ip_from 2405:b500::/32;
    set_real_ip_from 2405:8100::/32;
    set_real_ip_from 2a06:98c0::/29;
    set_real_ip_from 2c0f:f248::/32;
    # CloudflareのヘッダーからIPを取得
    real_ip_header CF-Connecting-IP;
    real_ip_recursive on;
    server {
        listen 80;
        server_name example.com;
        location /admin/ {
            allow 203.0.113.10;
            deny all;
        }
    }
}
Cloudflare IPリストの自動更新スクリプト:
#!/bin/bash
# /usr/local/bin/update-cloudflare-ips.sh
CLOUDFLARE_IPS="/etc/nginx/conf.d/cloudflare-ips.conf"
# Cloudflareの最新IPリストを取得
{
    echo "# Cloudflare IP Ranges - Auto-generated on $(date)"
    echo ""
    for ip in $(curl -s <https://www.cloudflare.com/ips-v4>); do
        echo "set_real_ip_from $ip;"
    done
    for ip in $(curl -s <https://www.cloudflare.com/ips-v6>); do
        echo "set_real_ip_from $ip;"
    done
    echo ""
    echo "real_ip_header CF-Connecting-IP;"
    echo "real_ip_recursive on;"
} > $CLOUDFLARE_IPS
# 設定をテストしてリロード
nginx -t && nginx -s reload
# cronで毎週実行
# crontab -e で以下を追加
0 3 * * 0 /usr/local/bin/update-cloudflare-ips.sh
複数段のプロキシチェーンがある場合
http {
    # 第1段階のプロキシ(ロードバランサー)
    set_real_ip_from 10.0.1.0/24;
    # 第2段階のプロキシ(CDN)
    set_real_ip_from 203.0.113.0/24;
    # X-Forwarded-Forの右側(最も信頼できる側)から評価
    real_ip_header X-Forwarded-For;
    real_ip_recursive on;
    # デバッグ用:XFFヘッダーの内容をログ出力
    log_format debug_xff '$remote_addr - XFF: "$http_x_forwarded_for" - Real: $realip_remote_addr';
    server {
        access_log /var/log/nginx/debug.log debug_xff;
    }
}
real_ip_recursiveの動作:
- off(デフォルト):XFFヘッダーの右端のIPのみを評価
- on:- set_real_ip_fromで信頼されたIPを右からスキップし、最初の信頼できないIPを採用
例:X-Forwarded-For: 203.0.113.50, 10.0.1.5, 10.0.1.10
- real_ip_recursive off→- 10.0.1.10を採用
- real_ip_recursive on→- 10.0.1.10と- 10.0.1.5は信頼済みなのでスキップし、- 203.0.113.50を採用
Docker・AWS・GCP環境でアクセス制限が効かない原因と対処法
コンテナやクラウド環境では、ネットワーク構成の複雑さからアクセス制限が期待通り動作しないことがよくあります。
Docker環境での問題と対処法
問題1:DockerコンテナのNginxが受け取るIPがDockerブリッジネットワークのIPになる
# 症状の確認
# Nginxのログを見ると、すべてのアクセスが172.17.0.1などになっている
docker logs nginx-container
解決策:ホストネットワークモードを使用
# docker-compose.yml
version: '3.8'
services:
  nginx:
    image: nginx:latest
    network_mode: "host"  # ホストのネットワークスタックを直接使用
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
注意: network_mode: "host"はポートマッピング(ports:)と併用できません。
解決策:リバースプロキシ経由でX-Forwarded-Forを使用
# docker-compose.yml
version: '3.8'
services:
  nginx-proxy:
    image: nginx:latest
    ports:
      - "80:80"
    volumes:
      - ./proxy.conf:/etc/nginx/nginx.conf:ro
    networks:
      - web
  app:
    image: nginx:latest
    volumes:
      - ./app.conf:/etc/nginx/nginx.conf:ro
    networks:
      - web
networks:
  web:
    driver: bridge
# proxy.conf(フロントのNginx)
http {
    server {
        listen 80;
        location / {
            proxy_pass <http://app:80>;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }
}
# app.conf(バックエンドのNginx)
http {
    # Dockerネットワークのゲートウェイを信頼
    set_real_ip_from 172.16.0.0/12;
    real_ip_header X-Forwarded-For;
    real_ip_recursive on;
    server {
        listen 80;
        location /admin/ {
            allow 203.0.113.10;  # これで実際のクライアントIPで制限可能
            deny all;
        }
    }
}
AWS環境での問題と対処法
問題1:ALBのヘルスチェックが403エラーになる
# 問題のある設定
location / {
    allow 203.0.113.0/24;  # オフィスIPのみ許可
    deny all;              # ALBのヘルスチェックもブロックされる
}
解決策:ALBのIPレンジを許可、またはヘルスチェック用エンドポイントを分離
# 方法1:ALBのIPレンジを許可
http {
    set_real_ip_from 10.0.0.0/8;  # VPC内部IP
    real_ip_header X-Forwarded-For;
    real_ip_recursive on;
    server {
        location / {
            allow 203.0.113.0/24;  # オフィスIP
            allow 10.0.0.0/8;      # ALBヘルスチェック用
            deny all;
        }
    }
}
# 方法2:ヘルスチェック専用エンドポイント
server {
    # ヘルスチェック用(制限なし)
    location /health {
        access_log off;
        return 200 "healthy\\n";
        add_header Content-Type text/plain;
    }
    # 本番エンドポイント(制限あり)
    location / {
        allow 203.0.113.0/24;
        deny all;
        proxy_pass <http://backend>;
    }
}
問題2:AWS WAFとNginx制限の二重管理
# ベストプラクティス:役割分担
# AWS WAF:SQLインジェクション、XSS、レート制限など
# Nginx:IP制限、Basic認証など細かいアクセス制御
server {
    location /admin/ {
        # NginxでIP制限
        allow 203.0.113.0/24;
        deny all;
        # WAFは通過済みなので、ここではアプリケーション保護に集中
        auth_basic "Admin Area";
        auth_basic_user_file /etc/nginx/.htpasswd;
    }
}
GCP(Google Cloud Platform)環境での対処法
問題:Cloud Load BalancingのIPレンジが広範囲
http {
    # GCPロードバランサーのIPレンジを信頼
    # 参考:<https://cloud.google.com/load-balancing/docs/https#how_the_load_balancer_communicates>
    set_real_ip_from 130.211.0.0/22;
    set_real_ip_from 35.191.0.0/16;
    # GCPではX-Forwarded-Forを使用
    real_ip_header X-Forwarded-For;
    real_ip_recursive on;
    server {
        location /admin/ {
            allow 203.0.113.10;
            deny all;
        }
    }
}
GCE(Compute Engine)でCloud Armorと併用する場合:
# Cloud Armorでグローバルな脅威対策
# Nginxでアプリケーション固有の細かい制御
server {
    # Cloud Armorが許可したトラフィックのみが到達
    location /api/ {
        # APIキー検証などアプリレベルの制御
        if ($http_x_api_key != "your-secret-key") {
            return 403;
        }
    }
    location /admin/ {
        # 特定IPのみ許可(Cloud Armorの追加保護)
        allow 203.0.113.10;
        deny all;
    }
}
403 Forbiddenが出る場合の原因とnginx.confの安全な書き方と確認手順
アクセス制限設定後に意図しない403エラーが発生する典型的なパターンと解決策を解説します。
典型的な403エラーの原因
原因1:allow/denyの記述順序ミス
# 間違った例
location /admin/ {
    deny all;              # 先に評価されるため
    allow 203.0.113.10;    # この行は実行されない
}
# 正しい例
location /admin/ {
    allow 203.0.113.10;
    deny all;
}
原因2:自分のIPアドレスを許可し忘れ
# SSH接続中に以下の設定を適用すると、ロックアウトされる
location / {
    allow 198.51.100.10;   # 別のIPのみ許可
    deny all;              # 自分のIPを書き忘れ!
}
# 対策:まず自分のIPを確認
# curl ifconfig.me
# または
# curl ipinfo.io/ip
原因3:locationブロックの継承ミス
# 外側のallow/denyは内側で上書きされる
location /admin/ {
    allow 192.168.1.0/24;  # これは無視される
    deny all;
    location ~ \\.php$ {
        allow 192.168.1.10;  # ここで上書き
        deny all;
        # PHP処理...
    }
}
# 正しい例:内側でも明示的に記述
location /admin/ {
    allow 192.168.1.0/24;
    deny all;
    location ~ \\.php$ {
        allow 192.168.1.0/24;  # 外側と同じ制限を再定義
        deny all;
        # PHP処理...
    }
}
原因4:リバースプロキシ環境でのIP誤認識
# realipモジュールの設定なしでプロキシIPで制限している
location /admin/ {
    allow 10.0.1.5;  # これはプロキシサーバーのIP
    deny all;        # 実際のクライアントIPではない
}
# 解決策:realipモジュールを正しく設定
http {
    set_real_ip_from 10.0.1.0/24;
    real_ip_header X-Forwarded-For;
    real_ip_recursive on;
}
安全な設定変更の手順
ステップ1:現在の設定をバックアップ
# タイムスタンプ付きバックアップ
sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.backup.$(date +%Y%m%d_%H%M%S)
# または特定サイトの設定
sudo cp /etc/nginx/sites-available/example.com /etc/nginx/sites-available/example.com.backup
ステップ2:設定ファイルを編集
sudo vim /etc/nginx/sites-available/example.com
ステップ3:構文チェック(最重要)
# 設定ファイルの構文チェック
sudo nginx -t
# 成功時の出力
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
# エラー時の出力例
nginx: [emerg] "allow" directive is not allowed here in /etc/nginx/sites-enabled/example.com:15
nginx: configuration file /etc/nginx/nginx.conf test failed
ステップ4:設定の段階的適用(推奨)
# まず緩い制限でテスト
location /admin/ {
    allow 203.0.113.0/24;  # 広めの範囲で許可
    deny all;
}
# 動作確認後、厳格化
location /admin/ {
    allow 203.0.113.10;    # 特定IPのみに絞る
    deny all;
}
ステップ5:リロード(ダウンタイムなし)
# graceful reload(既存の接続は維持される)
sudo nginx -s reload
# または
sudo systemctl reload nginx
ステップ6:アクセステスト
# 許可されたIPからテスト
curl -I <https://example.com/admin/>
# 拒否されるべきIPからテスト(別サーバーから)
curl -I <https://example.com/admin/>
# HTTP/1.1 403 Forbidden が返ればOK
トラブルシューティング用のデバッグ設定
http {
    # デバッグ用ログフォーマット
    log_format debug_ip '$remote_addr - $realip_remote_addr - '
                        '"$http_x_forwarded_for" - $request - $status';
    server {
        # デバッグログを有効化
        access_log /var/log/nginx/debug.log debug_ip;
        error_log /var/log/nginx/error.log debug;
        location /admin/ {
            # テスト用:実際の$remote_addrを返す
            add_header X-Debug-Remote-Addr $remote_addr always;
            add_header X-Debug-Real-IP $realip_remote_addr always;
            add_header X-Debug-XFF $http_x_forwarded_for always;
            allow 203.0.113.10;
            deny all;
        }
    }
}
# リアルタイムでログを監視
sudo tail -f /var/log/nginx/debug.log
# エラーログから403関連のみ抽出
sudo grep "403" /var/log/nginx/error.log
緊急時のロールバック手順
# 設定に問題があり、即座に戻す必要がある場合
sudo cp /etc/nginx/nginx.conf.backup.20241030_143000 /etc/nginx/nginx.conf
sudo nginx -t
sudo nginx -s reload
# Dockerの場合
docker cp nginx.conf.backup nginx-container:/etc/nginx/nginx.conf
docker exec nginx-container nginx -t
docker exec nginx-container nginx -s reload
本番環境での安全な適用チェックリスト
これらの対策を実施することで、リバースプロキシやクラウド環境でも確実にアクセス制限を機能させ、トラブルを未然に防ぐことができます。
よくある質問(FAQ)
Nginxのアクセス制限に関して、実務でよく寄せられる質問とその解決策をまとめました。
- 
連続アクセス(DoS/DDoS)対策としてアクセス数を制限する方法は? 
- 
IP制限だけでは防げない連続アクセスや大量リクエストに対しては、Nginxの limit_req_module(リクエスト数制限)とlimit_conn_module(同時接続数制限)を使用します。リクエスト数制限(Rate Limiting)の基本設定 http { # ゾーン定義:IPアドレスごとに10MBのメモリを使用 # 1秒あたり10リクエスト(= 1リクエスト/100ms)まで許可 limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s; # より厳格な制限用(管理画面など) limit_req_zone $binary_remote_addr zone=admin:10m rate=1r/s; server { listen 80; server_name example.com; # サイト全体に緩めの制限 location / { limit_req zone=general burst=20 nodelay; # burst=20: 瞬間的に20リクエストまでキューイング # nodelay: キューイングせず即座に処理(バースト分も制限に含む) root /var/www/html; } # 管理画面には厳格な制限 location /admin/ { limit_req zone=admin burst=5; # nodelay無し: バースト分はキューイングして順次処理 allow 192.168.1.0/24; deny all; } } }同時接続数の制限 http { # IPアドレスごとの同時接続数を制限 limit_conn_zone $binary_remote_addr zone=conn_limit:10m; server { listen 80; # サーバー全体で同時接続10まで limit_conn conn_limit 10; location /download/ { # ダウンロードエリアは1接続のみ(並列ダウンロード防止) limit_conn conn_limit 1; # 帯域制限も併用(1接続あたり500KB/s) limit_rate 500k; } } }API保護の実践的な設定例 http { # APIエンドポイント用:より厳格な制限 limit_req_zone $binary_remote_addr zone=api:10m rate=5r/s; limit_conn_zone $binary_remote_addr zone=api_conn:10m; # 特定のAPIキーを持つユーザーは緩和 map $http_x_api_key $api_user_tier { default "free"; "premium-key-12345" "premium"; "enterprise-key-67890" "enterprise"; } # ユーザー層ごとの制限ゾーン limit_req_zone $binary_remote_addr zone=api_free:10m rate=5r/s; limit_req_zone $binary_remote_addr zone=api_premium:10m rate=50r/s; server { listen 80; location /api/ { # 基本的な接続数制限 limit_conn api_conn 5; # ユーザー層に応じたレート制限 if ($api_user_tier = "free") { set $limit_zone api_free; } if ($api_user_tier = "premium") { set $limit_zone api_premium; } limit_req zone=api_free burst=10 nodelay; # 制限超過時のカスタムエラー limit_req_status 429; proxy_pass http://api_backend; } } }制限超過時のカスタムレスポンス http { limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s; # 429エラー用のカスタムページ server { listen 80; location / { limit_req zone=general burst=20 nodelay; limit_req_status 429; error_page 429 /429.html; } location = /429.html { root /var/www/html/errors; internal; add_header Retry-After 60 always; } } }ログイン試行の保護(ブルートフォース対策) http { # ログインエンドポイント専用:1分あたり5回まで limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m; server { location = /login { limit_req zone=login burst=2 nodelay; # 制限超過は即座にブロック limit_req_status 429; proxy_pass <http://backend>; } # WordPress wp-login.php location = /wp-login.php { limit_req zone=login burst=3; allow 192.168.1.0/24; # IP制限と併用 deny all; include fastcgi_params; fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; } } }重要なパラメータの解説- rate=10r/s:1秒あたり10リクエスト(平均)
- rate=1r/m:1分あたり1リクエスト(より厳格)
- burst=20:瞬間的なアクセス集中を20リクエストまで許容
- nodelay:バースト分も即座に処理(遅延なし)
- $binary_remote_addr:IPアドレスのバイナリ表現(メモリ効率が良い)
 
- 
IP制限を一時的に解除する簡単な方法は? 
- 
メンテナンス作業や緊急時に、設定ファイルを編集せずにIP制限を一時解除する方法をいくつか紹介します。 方法1:環境変数とmapディレクティブを使用 http { # 環境変数でメンテナンスモードを制御 map $maintenance_mode $ip_check_enabled { default 1; "off" 0; } server { listen 80; location /admin/ { # メンテナンスモードがoffなら制限をスキップ if ($ip_check_enabled = 0) { set $allow_access 1; } if ($ip_check_enabled = 1) { set $allow_access 0; # 通常のIP制限 allow 192.168.1.0/24; deny all; } } } }# 制限を一時解除 sudo nginx -g 'env maintenance_mode=off;' # 通常モードに戻す sudo nginx -s reload方法2:includeファイルを使った動的制御 # /etc/nginx/sites-available/example.com server { location /admin/ { # 外部ファイルから制限をインクルード include /etc/nginx/conf.d/ip-restrictions.conf; # その他の設定... } }# /etc/nginx/conf.d/ip-restrictions.conf(通常時) allow 192.168.1.0/24; allow 203.0.113.10; deny all;# /etc/nginx/conf.d/ip-restrictions.conf(一時解除時) allow all; # すべてのallow/denyをコメントアウトまたは削除# 一時解除:ファイルを空にするかallow allのみに echo "allow all;" | sudo tee /etc/nginx/conf.d/ip-restrictions.conf sudo nginx -t && sudo nginx -s reload # 元に戻す:バックアップから復元 sudo cp /etc/nginx/conf.d/ip-restrictions.conf.backup /etc/nginx/conf.d/ip-restrictions.conf sudo nginx -s reload方法3:特別なクエリパラメータで一時アクセス許可 http { # 秘密のトークンを設定 map $arg_token $allow_by_token { default 0; "secret-emergency-token-2024" 1; } server { location /admin/ { # トークンがあれば制限をバイパス if ($allow_by_token = 1) { set $bypass 1; } if ($bypass != 1) { allow 192.168.1.0/24; deny all; } # 通常の処理... } } }使用例: <https://example.com/admin/?token=secret-emergency-token-2024>セキュリティ注意: - トークンは定期的に変更する
- アクセスログからトークンが漏洩する可能性があるため、使用後は速やかに変更
- HTTPS環境でのみ使用する
 方法4:Basic認証でIP制限をバイパス server { location /admin/ { satisfy any; # IP制限またはBasic認証のいずれかでOK # IP制限 allow 192.168.1.0/24; deny all; # Basic認証(緊急時のバックアップアクセス) auth_basic "Emergency Access"; auth_basic_user_file /etc/nginx/.htpasswd_emergency; } }# 緊急用ユーザーを作成 sudo htpasswd -c /etc/nginx/.htpasswd_emergency emergency_user通常は社内IPからアクセス、緊急時は外出先から認証してアクセス可能。 
- 
Basic認証とIP制限を両方かける場合の推奨設定は? 
- 
セキュリティを最大化するため、IP制限とBasic認証を組み合わせる「多層防御」が推奨されます。 パターン1:両方必須(AND条件)- 最もセキュア server { listen 443 ssl; server_name example.com; location /admin/ { # デフォルトはsatisfy all(すべての条件を満たす必要がある) satisfy all; # 第1層:IP制限 allow 192.168.1.0/24; # 社内ネットワーク allow 203.0.113.10; # VPN deny all; # 第2層:Basic認証 auth_basic "Admin Area - Restricted"; auth_basic_user_file /etc/nginx/.htpasswd; # 第3層(推奨):クライアント証明書認証 ssl_client_certificate /etc/nginx/ssl/ca.crt; ssl_verify_client on; proxy_pass <http://backend>; } }パターン2:いずれか1つ(OR条件)- 柔軟性重視 server { location /admin/ { satisfy any; # いずれか1つの条件でアクセス可能 # 社内からはIP認証のみでOK allow 192.168.1.0/24; deny all; # 外部からはBasic認証が必要 auth_basic "Admin Area"; auth_basic_user_file /etc/nginx/.htpasswd; } }ユースケース: - 社内ネットワークからはパスワード入力不要
- 外出先からはパスワード認証で管理画面にアクセス
 パターン3:段階的なセキュリティレベル server { # レベル1:サイト閲覧(制限なし) location / { root /var/www/html; } # レベル2:ユーザーダッシュボード(Basic認証のみ) location /dashboard/ { auth_basic "User Dashboard"; auth_basic_user_file /etc/nginx/.htpasswd_users; proxy_pass <http://backend>; } # レベル3:管理画面(IP制限 AND Basic認証) location /admin/ { satisfy all; allow 192.168.1.0/24; deny all; auth_basic "Admin Area"; auth_basic_user_file /etc/nginx/.htpasswd_admin; proxy_pass <http://backend>; } # レベル4:システム設定(IP制限 AND 強力な認証 AND HTTPS必須) location /system/ { # HTTPS強制 if ($scheme != "https") { return 301 https://$server_name$request_uri; } satisfy all; # 特定IPのみ allow 192.168.1.10; deny all; # クライアント証明書必須 ssl_verify_client on; ssl_client_certificate /etc/nginx/ssl/ca.crt; auth_basic "System Configuration"; auth_basic_user_file /etc/nginx/.htpasswd_system; proxy_pass <http://backend>; } }htpasswdファイルの安全な作成と管理 # 初回作成(既存ファイルを上書き) sudo htpasswd -c /etc/nginx/.htpasswd admin_user # ユーザー追加(-cオプションなし) sudo htpasswd /etc/nginx/.htpasswd another_user # BCryptアルゴリズムで強力なハッシュ化(推奨) sudo htpasswd -B -c /etc/nginx/.htpasswd admin_user # パスワードをコマンドラインで指定(スクリプト用、非推奨) sudo htpasswd -b /etc/nginx/.htpasswd user_name password123 # ユーザー削除 sudo htpasswd -D /etc/nginx/.htpasswd user_to_delete # パーミッション設定(重要) sudo chmod 640 /etc/nginx/.htpasswd sudo chown root:nginx /etc/nginx/.htpasswdBasic認証とIP制限のログ出力 http { # カスタムログフォーマット:認証状況を記録 log_format auth_log '$remote_addr - $remote_user [$time_local] ' '"$request" $status $body_bytes_sent ' '"$http_referer" "$http_user_agent" ' 'auth_result="$sent_http_www_authenticate"'; server { location /admin/ { access_log /var/log/nginx/admin_auth.log auth_log; satisfy all; allow 192.168.1.0/24; deny all; auth_basic "Admin"; auth_basic_user_file /etc/nginx/.htpasswd; } } }# 認証失敗をモニタリング sudo tail -f /var/log/nginx/admin_auth.log | grep "401" # 特定ユーザーのアクセスを追跡 sudo grep "admin_user" /var/log/nginx/admin_auth.logブラウザの認証情報キャッシュを制御 location /admin/ { auth_basic "Admin Area"; auth_basic_user_file /etc/nginx/.htpasswd; # 認証情報をキャッシュさせない(より安全) add_header Cache-Control "no-store, no-cache, must-revalidate" always; add_header Pragma "no-cache" always; # またはログアウト用エンドポイント } # ログアウト用 location /logout { # 401を返して認証情報をクリア return 401; add_header WWW-Authenticate 'Basic realm="Logged Out"' always; }推奨セキュリティ設定のまとめ 本番環境での推奨構成: server { listen 443 ssl http2; server_name example.com; # SSL/TLS設定 ssl_certificate /etc/nginx/ssl/cert.pem; ssl_certificate_key /etc/nginx/ssl/key.pem; ssl_protocols TLSv1.2 TLSv1.3; # 管理画面:多層防御 location /admin/ { # 1. HTTPS必須 # 2. IP制限(社内ネットワークのみ) # 3. Basic認証 # 4. レート制限 satisfy all; allow 192.168.1.0/24; deny all; auth_basic "Admin Area"; auth_basic_user_file /etc/nginx/.htpasswd; limit_req zone=admin burst=5; # セキュリティヘッダー add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always; proxy_pass <http://backend>; } }
まとめ
さて、ここまでNginxを用いたアクセス制限について、基本のIP制限から、リバースプロキシ環境での応用、さらにはセキュリティ強化のためのレート制限まで、実践的な設定方法を網羅的に解説してきました。Webサービスの安定稼働とセキュリティ維持は、私たちエンジニアの責務のなかでも特に重要度の高い領域です。
Nginxのアクセス制御機能は非常に強力ですが、設定ミスは「必要なアクセスまで遮断してしまう」という大きなトラブルに直結します。そのため、設定を反映させる前には必ずテストを行い、安全にリロードする手順を徹底してください。この記事を活用して、自信を持ってサーバーの防御を固めていきましょう。
Nginxの設定ファイルは、サービスの要塞となる設計図です。もし新たな課題や疑問点が生じたら、いつでもこの記事を読み返したり、追加で検索したりして、スキルアップを続けてください。
あわせて読みたい



 
  
