2026年10月2日

2026年10月2日

Nginxのmap機能で条件分岐処理をする方法:ifを減らす実用パターン集

Nginx で「この条件のときだけ別の処理をしたい」と思ったら、if を重ねるより、map で条件を変数に変換し、その変数を各ディレクティブに渡す書き方が読みやすく、誤動作も起きにくくなります。この記事では、map を条件分岐に使うときの考え方と、ログの出し分け・アクセス制限の対象外指定・CORS の許可リスト・転送先の切り替え・メンテナンス表示といった実用パターンを、WordPress サイトでの使い方を交えて説明します。

map のハッシュ設定(map_hash_max_size など)の調整や、大量のリダイレクトを map で管理する方法は別記事で扱います。

なぜ if より map なのか

Nginx の if は、プログラミング言語の if 文とは動きが違います。特に location の中で使うと、内部的に別の設定ブロックが作られるため、if の外に書いた try_files や一部のヘッダーが効かなくなるなど、直感に反する結果になることがあります。公式の解説でも、location 内の if で安全なのは return と rewrite ... last 程度だとされています。

map はこの問題を避けられます。

  • 条件の判定を http ブロックで1か所にまとめ、結果を変数として受け取れる
  • 変数は実際に参照されたときに初めて評価されるため、使わない条件の分だけ処理が増えることはない
  • 「条件 → 値」の対応表になるので、後から条件を足しても見通しが崩れにくい

基本の書式は次のとおりです。map は http コンテキストにだけ書けます。

map $入力となる変数 $結果を入れる変数 {
    default     既定の値;
    キー1       値1;
    ~正規表現   値2;     # ~ は大文字小文字を区別、~* は区別しない
}

hostnames や volatile といった特別なパラメータには追加された版があり、古い Nginx では使えないものもあります。手元の nginx -v と公式ドキュメントの記載を照らし合わせてください(2026年10月現在)。

キーの照合は、文字列の一致が先、正規表現は書いた順に評価されます。正規表現の照合順に依存する書き方をすると、行を並べ替えただけで結果が変わるため、条件が重ならないキーにしておくのが安全です。

パターン1: 特定のリクエストをログに残さない

ヘルスチェックや監視サービスからの定期アクセスでログが埋まる場合、access_log の if= パラメータと map を組み合わせます。if= に渡した値が 0 または空のときは記録されません。

map $request_uri $loggable {
    default            1;
    /healthz           0;
    ~^/wp-cron\.php    0;
}

map $http_user_agent $loggable_ua {
    default            $loggable;   # 前の map の結果を引き継ぐ
    ~*uptime-monitor   0;
}

server {
    access_log /var/log/nginx/access.log main if=$loggable_ua;
}

2つ目の map の default に1つ目の結果を入れることで、「URL の条件」と「User-Agent の条件」のどちらかに当たれば記録しない、という OR 条件を作れます。このように map を連鎖させるのは、複数条件を扱うときの定番の書き方です。

ただしログを減らすと、障害や不正アクセスの調査に使える情報も減ります。除外は「不要だとはっきり分かっているもの」に限り、エラー(4xx・5xx)は残す設計にしておくと後で困りません。

パターン2: レート制限の対象から一部を外す

limit_req_zone は、キーの値が空のリクエストを集計しません。この性質を利用すると、社内ネットワークや監視サーバーだけを制限の対象外にできます。IP アドレスの範囲(CIDR)で判定するときは map ではなく geo を使い、その結果を map で変換します。

geo $limit_exempt {
    default        0;
    192.0.2.0/24   1;   # 社内ネットワーク(例)
    203.0.113.10   1;   # 監視サーバー(例)
}

map $limit_exempt $limit_key {
    0  $binary_remote_addr;
    1  "";
}

limit_req_zone $limit_key zone=login:10m rate=5r/m;

server {
    location = /wp-login.php {
        limit_req zone=login burst=3 nodelay;
        # 以降は PHP へ渡す通常の設定
    }
}

/wp-login.php への総当たりを抑えつつ、管理者がいる拠点からのアクセスだけは制限しない、という WordPress でよくある要件をこれで表現できます。ロードバランサや CDN の背後では $binary_remote_addr がプロキシの IP になるため、real_ip モジュールで実際のクライアント IP を復元してから使います。

パターン3: CORS で許可するオリジンを絞る

Access-Control-Allow-Origin に * を返すのは簡単ですが、Cookie つきのリクエストでは使えず、必要以上に広く許可することにもなります。map で許可リストに載ったオリジンだけをそのまま返す形にすると安全です。

map $http_origin $cors_origin {
    default                         "";
    "https://app.example.com"       $http_origin;
    "https://staging.example.com"   $http_origin;
}

server {
    location /wp-json/ {
        add_header Access-Control-Allow-Origin $cors_origin always;
        add_header Vary Origin always;
        try_files $uri $uri/ /index.php?$args;
    }
}

add_header は値が空のときヘッダー自体を出力しないため、許可リストにないオリジンには何も返りません。Vary: Origin を付けておくと、CDN やブラウザのキャッシュがオリジンごとに区別されます。正規表現でサブドメインをまとめて許可する場合は、~^https://([a-z0-9-]+\.)?example\.com$ のように先頭と末尾を固定しないと、example.com.attacker.example のような別ドメインまで通してしまうので注意してください。

パターン4: Cookie やヘッダーで転送先を切り替える

新しいアプリケーションサーバーを一部の利用者だけで試したいときは、map の結果を proxy_pass に渡します。

upstream app_stable { server 127.0.0.1:8080; }
upstream app_canary { server 127.0.0.1:8081; }

map $cookie_canary $app_backend {
    default  app_stable;
    "1"      app_canary;
}

server {
    location / {
        proxy_pass http://$app_backend;
    }
}

値が upstream で定義した名前に一致するので、DNS の名前解決(resolver)なしで動きます。値を自由な文字列にすると、Nginx は実行時にホスト名として解決しようとするため、意図しない宛先に接続しないよう、取り得る値はすべて map の中で固定しておきます。

パターン5: メンテナンス中は管理者以外に 503 を返す

geo $is_admin_ip {
    default       0;
    192.0.2.0/24  1;
}

map $is_admin_ip $maintenance {
    default  1;   # メンテナンス中は 1、終わったら 0 に戻す
    1        0;
}

server {
    location / {
        if ($maintenance) { return 503; }
        try_files $uri $uri/ /index.php?$args;
    }
    error_page 503 /maintenance.html;
    location = /maintenance.html { root /var/www/static; internal; }
}

ここでは if を使っていますが、中身は return だけなので問題の起きにくい使い方です。切り替えは map の default を書き換えて reload するだけで済みます。503 を返すことで、検索エンジンにも一時的な停止であることが伝わります。

反映と確認のしかた

map は http ブロックに書くため、conf.d/ に maps.conf のような専用ファイルを作ってまとめると管理しやすくなります。変更後はいつもどおり文法チェックをしてから再読み込みします。

sudo cp -a /etc/nginx /etc/nginx.bak-$(date +%Y%m%d)
sudo nginx -t && sudo systemctl reload nginx

map の結果が期待どおりかは、一時的にレスポンスヘッダーへ出すと確認しやすくなります。確認が終わったら、内部の判定を外部に見せないよう削除してください。

add_header X-Debug-Backend $app_backend always;   # 確認用。終わったら消す
curl -sI https://example.com/ | grep -i x-debug
curl -sI --cookie "canary=1" https://example.com/ | grep -i x-debug

本番に入れる前に、ステージング環境で各条件に当たるリクエストと当たらないリクエストの両方を試しておくと、正規表現の書き損じを早く見つけられます。

まとめ

  • 条件分岐は map で変数に変換し、その変数を各ディレクティブに渡すと if の落とし穴を避けられる
  • map は http コンテキストに書き、値は参照されたときにだけ評価される
  • map を連鎖させると OR 条件を、geo と組み合わせると IP 範囲の条件を扱える
  • ログの if=、limit_req_zone の空キー、add_header の空値といった「空なら何もしない」性質と相性がよい
  • 正規表現は先頭と末尾を固定し、結果はヘッダーに一時表示して確かめてから本番に入れる

お気軽にご相談ください

お見積りへ お問い合わせへ
▲