Nginxで守る入門:Basic認証とIP制限(allow/deny)

管理画面や内部向けAPI、ステージング環境などを“とりあえず守る”ときに、Nginxの Basic認証IP制限 は即効性が高い定番手段です。
アプリ側に手を入れずに「入口(Nginx)だけでガード」をかけられるので、実務でも出番が多いです。

この記事では、

  • Basic認証(ユーザー名/パスワード)でアクセスを制限する
  • IP制限(allow/deny)で特定ネットワーク以外を遮断する
  • 「IPがOKなら認証なし」「IPがダメなら認証必須」などの“組み合わせ”を実現する

までを、Dockerで再現できるハンズオン付きで解説します。


まず整理:Basic認証とIP制限は何が違う?

Basic認証(auth_basic)

  • ブラウザがユーザー/パスワード入力を要求される方式
  • 認証が通ればアクセス可能
  • 使いどころ:管理画面、簡易な保護、ステージング環境、緊急の防御

注意点

  • HTTPSなしで使うと、資格情報が盗まれるリスクが大きい(必ずHTTPSとセット推奨)
  • “強固な認証基盤”というより、入口の簡易ガード

IP制限(allow/deny)

  • クライアントのIPで通す/拒否する方式
  • そもそもブロックするのでシンプル
  • 使いどころ:社内ネットワーク限定、監視用エンドポイント、管理者のみの画面

注意点

  • 固定IPや社内VPN前提が多い
  • プロキシ/CDNを挟むと「本当のクライアントIP」をNginxが見失うことがある(後述)

Nginxの基本:IP制限は “locationごと” にかけられる

Nginxは server { ... } の中で location { ... } を分けられるので、

  • / は公開
  • /admin はBasic認証
  • /internal はIP制限

のような「パス単位の防御」が得意です。まずはこの感覚を掴むのが最短です。


ハンズオン0:準備(DockerでNginxを動かす)

ディレクトリ構成

nginx-guard/
  ├─ html/
  │   ├─ index.html
  │   └─ admin.html
  ├─ nginx/
  │   ├─ default.conf
  │   └─ .htpasswd
  └─ docker-compose.yml

HTMLを用意

html/index.html

<h1>Public Page</h1>
<p>ここは誰でも見られる想定</p>

html/admin.html

<h1>Admin Page</h1>
<p>ここは保護したい</p>

ハンズオン1:Basic認証をかける(auth_basic / auth_basic_user_file)

1) .htpasswd を作る(推奨:bcrypt)

一番楽なのは htpasswd コマンドです。ローカルに無い場合でも、Dockerで作れます。

Dockerで作る例(bcrypt)

プロジェクト直下で実行:

docker run --rm httpd:2.4-alpine htpasswd -nbB admin 'StrongPasswordHere' > nginx/.htpasswd
  • -n:ファイルに書かず標準出力へ
  • -b:パスワードをコマンド引数から渡す(履歴に残るので本番では注意)
  • -B:bcrypt(推奨)

本番では -b を避けて対話入力にする、もしくはCI/Secret管理で生成・配布するのが安全です。

生成された nginx/.htpasswd はこんな形式になります(例):

admin:$2y$05$....

2) Nginx設定を書く

nginx/default.conf

server {
  listen 80;
  server_name _;

  root /usr/share/nginx/html;
  index index.html;

  location / {
    try_files $uri $uri/ =404;
  }

  # /admin 配下をBasic認証で保護
  location /admin {
    auth_basic "Restricted Area";
    auth_basic_user_file /etc/nginx/.htpasswd;

    # 例として admin.html を返す
    try_files /admin.html =404;
  }
}

ポイント:

  • auth_basic の文字列はブラウザに表示される“realm”名
  • auth_basic_user_file.htpasswd のパス

3) docker-compose.yml

docker-compose.yml

services:
  nginx:
    image: nginx:alpine
    ports:
      - "8080:80"
    volumes:
      - ./html:/usr/share/nginx/html:ro
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
      - ./nginx/.htpasswd:/etc/nginx/.htpasswd:ro

4) 起動

docker compose up -d

5) 動作確認

  • http://localhost:8080/ → 認証なしで表示
  • http://localhost:8080/admin → ユーザー名/パスワードが要求される

curlで確認する場合:

curl -i http://localhost:8080/admin

401 Unauthorized が返ればOK(認証要求)

認証あり:

curl -i -u admin:StrongPasswordHere http://localhost:8080/admin

200 OK になれば成功です。


ハンズオン2:IP制限(allow/deny)をかける

次は「特定IPだけ許可、それ以外は拒否」をやります。

ローカル環境ではクライアントIPが 127.0.0.1(IPv6なら ::1)になることが多いです。
まずはローカル許可で体験し、そのあと “社内CIDR” の書き方を覚えるのが安全です。

1) 設定を追記

nginx/default.conf をこうします:

server {
  listen 80;
  server_name _;

  root /usr/share/nginx/html;
  index index.html;

  location / {
    try_files $uri $uri/ =404;
  }

  # /admin はBasic認証
  location /admin {
    auth_basic "Restricted Area";
    auth_basic_user_file /etc/nginx/.htpasswd;
    try_files /admin.html =404;
  }

  # /internal はIP制限(例:ローカルだけ許可)
  location /internal {
    allow 127.0.0.1;
    allow ::1;
    deny all;

    default_type text/plain;
    return 200 "internal ok\n";
  }
}

ポイント:

  • allow / deny は上から評価されます
  • 最後に deny all; を置くのが典型

2) 反映(再起動でもOK、ここではrestart)

docker compose restart nginx

3) 確認

curl -i http://localhost:8080/internal

ローカルからなら 200 が返る想定です。


実務パターン:CIDRで社内ネットワークだけ許可する

例(社内が 192.168.0.0/16 の場合):

location /internal {
  allow 192.168.0.0/16;
  deny all;
}

例(オフィス固定IPが 203.0.113.10 の場合):

location /internal {
  allow 203.0.113.10;
  deny all;
}

ハンズオン3:IPがOKなら認証不要、IPがダメなら認証必須(satisfy any)

ここが“入口設計”としてかなり便利なところです。

  • 社内(許可IP)からは スッと入れる
  • 社外からは パスワードが必要
  • どちらも満たさない場合はブロック

Nginxでは satisfy any; を使うと実現できます。

設定例:/admin を「社内IP or Basic認証」で許可

nginx/default.conf/admin を差し替え:

location /admin {
  # どれか1つ満たせばOK(IP許可 or 認証OK)
  satisfy any;

  # IP制限(例:社内VPN/固定IP)
  allow 127.0.0.1;
  allow ::1;
  # allow 203.0.113.10;       # 固定IP例
  # allow 192.168.0.0/16;     # 社内CIDR例
  deny all;

  # Basic認証
  auth_basic "Restricted Area";
  auth_basic_user_file /etc/nginx/.htpasswd;

  try_files /admin.html =404;
}

重要な挙動

  • 許可IPなら auth_basic があっても入力なしで通る
  • 許可IPじゃなければ、Basic認証が求められる
  • IPもダメ、認証もダメなら通れない

よくある落とし穴・事故ポイント(ここは実務で効く)

1) “本当のクライアントIP” が見えていない問題

CloudflareやALB、リバプロを挟むと、Nginxから見える接続元IPが「プロキシのIP」になります。
この状態で allow/deny すると、意図せず全拒否 / 全許可になりがちです。

対策は、プロキシが付与するヘッダ(例:X-Forwarded-For や Cloudflare の CF-Connecting-IP)を使って、Nginxに“真のIP”を認識させること。

例(一般的な X-Forwarded-For を使う場合の考え方):

  • real_ip_header X-Forwarded-For;
  • set_real_ip_from <信頼できるプロキシのIPレンジ>;

※ここは環境依存が強いので、VPS直置き・Cloudflare利用など前提が決まったら、その前提で設定を固めるのが安全です。

2) 403と401が混ざって混乱する

  • IP制限で落ちる → 403 Forbidden
  • Basic認証が必要 → 401 Unauthorized(認証ダイアログが出る)

どっちで落ちているか、curlのステータスで切り分けると早いです。

3) .htpasswd をGitに入れてしまう

ハッシュ化されていても、漏洩リスクや運用上の事故につながるので基本は避けたいです。
本番では Secret 管理(Kubernetes Secret / Docker secret / CIの秘匿変数)などに寄せるのがおすすめです。

4) パスワードを弱くしがち

Basic認証は突破される前提で運用するものではありませんが、弱いパスワードは事故の温床です。
最低限、長めのランダム文字列(英数記号)に寄せるのが安全です。


まとめ:Nginxの入口ガードは “小さく強い”

  • Basic認証:すぐ使える。ステージング/管理画面の簡易保護に強い(HTTPSとセット)
  • IP制限:シンプルで強力。社内限定や監視系に効く
  • 組み合わせ(satisfy any):現場で気持ちよく運用できるパターンが作れる

投稿日

カテゴリー:

投稿者:

タグ:

コメント

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です