2026年7月7日

2026年7月7日

Nginx auth_requestで外部認証と連携する設定|WordPress管理画面・APIの保護

Nginx の auth_request は、リクエストを処理する前に別の URL へサブリクエストを送り、その応答コードでアクセスを許可するかどうかを決める仕組みです。認証そのものは外部の認証サーバー(自作のサービスや oauth2-proxy など)が行い、Nginx はその結果に従って通すか止めるかだけを判断します。

この記事では、モジュールの確認から、WordPress の管理画面だけに認証をかける location の組み方、認証サーバーが返したユーザー情報を PHP や API に渡す方法、認証に失敗したときの扱いまでを、nginx.org の公式ドキュメントに沿って説明します(2026年10月時点)。

auth_request の動き

auth_request を設定した location にリクエストが来ると、Nginx は指定した URI へサブリクエストを送り、応答コードを次のように扱います。

サブリクエストの応答 元のリクエストの扱い
2xx アクセスを許可して通常どおり処理する
401 401 で拒否する(認証サーバーが返した WWW-Authenticate ヘッダーもクライアントに渡る)
403 403 で拒否する
それ以外(認証サーバーの停止、5xx、302 など) エラーとして扱われ、500 になる

認証サーバーが 302 でログイン画面へ誘導しても、Nginx はそれをエラーとみなします。ログイン画面へのリダイレクトは、認証サーバーではなく Nginx 側の error_page で行います(後述)。

モジュールが組み込まれているか確認する

ngx_http_auth_request_module は、ソースからビルドするときの既定では組み込まれず、--with-http_auth_request_module を付けたときに有効になるモジュールです。nginx.org が配布する公式パッケージや、Ubuntu・Debian・RHEL 系などのディストリビューションのパッケージには、多くの場合含まれています。Nginx の商用版(NGINX Plus)が必要というわけではありません。

手元の Nginx に含まれているかは、次のコマンドで確認できます。

nginx -V 2>&1 | tr ' ' '\n' | grep auth_request
# --with-http_auth_request_module と表示されれば使える

何も表示されない場合は、nginx.org の公式リポジトリのパッケージに入れ替えるか、このオプションを付けてビルドし直します。

認証サーバーに求める動き

認証サーバーは、次のように応答するものを用意します。

  • 認証に使う Cookie や Authorization ヘッダーを調べ、正しければ 2xx(200 や 204 など)を返す
  • 未ログインなら 401、ログイン済みでも権限が無ければ 403 を返す
  • 必要なら、ユーザー名などを X-Auth-User のような応答ヘッダーで返す

自作する場合のほか、既存のソフトとしては oauth2-proxy がこの用途に対応しています。oauth2-proxy の /oauth2/auth は 202 か 401 だけを返すエンドポイントで、Nginx の auth_request から呼ぶ構成が公式ドキュメントで案内されています。

以下では、認証サーバーが 127.0.0.1:8081 で動き、/auth で判定するものとして書きます。設定の前に、認証サーバー単体の応答を確認しておきます。

# Cookie 無し → 401 が返ることを確認
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8081/auth

# 有効なセッション Cookie 付き → 2xx が返ることを確認(Cookie 名と値は認証サーバーに合わせる)
curl -s -o /dev/null -w '%{http_code}\n' -b 'session=有効なセッションID' http://127.0.0.1:8081/auth

基本の設定: 内部専用の認証 location

サブリクエストの送り先は、外部から直接呼べない internal の location にします。

location = /_auth {
    internal;                              # 外部からの直接アクセスは 404
    proxy_pass http://127.0.0.1:8081/auth;
    proxy_pass_request_body off;           # 本文は認証サーバーに送らない
    proxy_set_header Content-Length "";
    proxy_set_header X-Original-URI $request_uri;
    proxy_set_header X-Original-Method $request_method;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_connect_timeout 2s;              # 認証サーバーが応答しないときに長く待たない
    proxy_read_timeout 5s;
}
  • proxy_pass_request_body off と Content-Length "" は公式ドキュメントの例にある組み合わせで、アップロードのような大きな本文が認証サーバーにも送られるのを防ぎます。
  • サブリクエストには元のリクエストのヘッダーがそのまま付くので、Cookie や Authorization を改めて proxy_set_header で転送する必要はありません。
  • 認証サーバーには元の URL が届かないため、URL ごとに判定したい場合は X-Original-URI のように自分でヘッダーを足します。

WordPress の管理画面だけに認証をかける

WordPress で「公開ページは誰でも見られ、管理画面とログイン画面だけ認証を通す」構成にする場合、location の優先順位に注意が要ります。

Nginx は、前方一致の location(location /wp-admin/)より、正規表現の location(location ~ \.php$)を優先します。そのため、location /wp-admin/ { auth_request ...; } と書いても、/wp-admin/index.php などの PHP ファイルへのリクエストは \.php$ の location で処理され、認証がかかりません。逆に \.php$ の location に auth_request を書くと、トップページを含むすべての PHP(つまりサイト全体)に認証がかかってしまいます。

これを避けるには、^~ を付けた前方一致の location で正規表現より優先させ、その中に PHP 用の location を入れ子で書きます。

server {
    listen 443 ssl;
    http2 on;
    server_name example.com;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    root /var/www/html;
    index index.php;

    # 認証サーバーへのサブリクエスト(内部専用)
    location = /_auth {
        internal;
        proxy_pass http://127.0.0.1:8081/auth;
        proxy_pass_request_body off;
        proxy_set_header Content-Length "";
        proxy_set_header X-Original-URI $request_uri;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_connect_timeout 2s;
        proxy_read_timeout 5s;
    }

    # 公開ページ(認証なし)
    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    # 管理画面: ^~ で正規表現の location より優先させる
    location ^~ /wp-admin/ {
        auth_request /_auth;
        auth_request_set $auth_user $upstream_http_x_auth_user;
        error_page 401 = @login_required;

        location ~ \.php$ {
            try_files $uri =404;
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_param NGINX_AUTH_USER $auth_user;  # PHP では $_SERVER['NGINX_AUTH_USER']
            fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        }
    }

    # admin-ajax.php は公開ページのフォームなどからも呼ばれるため認証の対象外にする
    location = /wp-admin/admin-ajax.php {
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    }

    # ログイン画面
    location = /wp-login.php {
        auth_request /_auth;
        error_page 401 = @login_required;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    }

    # そのほかの PHP(認証なし)
    location ~ \.php$ {
        try_files $uri =404;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    }

    # 未認証(401)のときは認証サーバーのログイン画面へ
    location @login_required {
        return 302 https://auth.example.com/login?rd=$scheme://$host$request_uri;
    }
}

設定のポイントは次のとおりです。

  • 完全一致(=)の location は ^~ より優先されるので、/wp-admin/admin-ajax.php だけを認証の対象から外せます。
  • auth_request は親の location から入れ子の location に引き継がれます。入れ子の中で外したい場合は auth_request off; を書きます。
  • PHP-FPM のソケットのパスは環境によって違います(Ubuntu 24.04 の PHP 8.3 なら /run/php/php8.3-fpm.sock)。ls /run/php/ で確認してください。
  • listen 443 ssl http2; という書き方は Nginx 1.25.1 で非推奨になり、http2 on; を別に書く形に変わっています。古い Nginx では http2 on; が使えないので、nginx -v で版を確認します。
  • error_page 401 = @login_required; の = は、応答コードを名前付き location の結果(ここでは 302)に置き換える指定です。403 は既定のエラーページのままにしています。

この構成でも WordPress 自体のログインは別に必要です。auth_request は、WordPress のログイン画面に届く前にもう一段の関門を置くものと考えてください。

認証結果を PHP に渡す

認証サーバーが応答ヘッダーで返した値は、auth_request_set で変数に入れられます。応答ヘッダー X-Auth-User は、変数 $upstream_http_x_auth_user で読めます。

上の例では、fastcgi_param NGINX_AUTH_USER $auth_user; で PHP に渡しています。名前を HTTP_ で始めないのは、Nginx がクライアントの送ったリクエストヘッダーを HTTP_ で始まる名前で PHP に渡すためです。HTTP_X_AUTH_USER のような名前にすると、クライアントが自分で X-Auth-User ヘッダーを送った場合と区別しにくくなります。

<?php
// wp-admin で動くコードの中で、Nginx が渡した認証ユーザー名を読む
$auth_user = isset( $_SERVER['NGINX_AUTH_USER'] ) ? sanitize_text_field( wp_unslash( $_SERVER['NGINX_AUTH_USER'] ) ) : '';
if ( '' !== $auth_user ) {
	error_log( 'auth_request user: ' . $auth_user );
}

API をトークンで守り、JSON でエラーを返す

WordPress とは別のバックエンド(API サーバー)の前に Nginx を置き、Authorization: Bearer のトークンを認証サーバーに検証させる例です。

upstream token_auth_service {
    server 127.0.0.1:3001;
    keepalive 10;
}

upstream api_backend {
    server 127.0.0.1:9000;
}

server {
    listen 443 ssl;
    http2 on;
    server_name api.example.com;

    ssl_certificate     /etc/letsencrypt/live/api.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;

    location = /_token_auth {
        internal;
        proxy_pass http://token_auth_service/validate;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_pass_request_body off;
        proxy_set_header Content-Length "";
        proxy_set_header X-Original-URI $request_uri;
    }

    location /api/ {
        auth_request /_token_auth;
        auth_request_set $api_user_id $upstream_http_x_user_id;

        proxy_pass http://api_backend;
        proxy_set_header X-User-ID $api_user_id;

        error_page 401 = @api_unauthorized;
    }

    location @api_unauthorized {
        default_type application/json;
        return 401 '{"error":"unauthorized","message":"valid token required"}';
    }
}
  • keepalive で認証サーバーとの接続を使い回すには、proxy_http_version 1.1; と proxy_set_header Connection ""; が必要です。
  • proxy_set_header X-User-ID で設定したヘッダーは、クライアントが同じ名前のヘッダーを送ってきても、その値ではなく設定した値でバックエンドに渡ります。ただし、バックエンドが Nginx を通さずに直接届く状態だと偽装できるので、バックエンドは 127.0.0.1 など外から届かないアドレスで待ち受けます。

REST API の一部を守るときの注意

WordPress の REST API の一部(たとえば /wp-json/wp/v2/users)だけを location で保護することもできますが、WordPress の REST API は /?rest_route=/wp/v2/users のようにクエリ文字列でも呼べます。Nginx の location はクエリ文字列を見ないため、パスだけの保護は回避されます。ユーザー一覧の公開を止めたい場合は、WordPress 側でも rest_endpoints フィルターなどで制御します。

動作確認とログ

設定を変えたら、構文を確認してから読み込み直します。

sudo nginx -t && sudo systemctl reload nginx

# 未認証で管理画面 → 302(ログイン画面へ)
curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' https://example.com/wp-admin/

# 公開ページ → 200
curl -s -o /dev/null -w '%{http_code}\n' https://example.com/

# admin-ajax.php は認証の対象外(WordPress が 400 などを返せば Nginx は通している)
curl -s -o /dev/null -w '%{http_code}\n' https://example.com/wp-admin/admin-ajax.php

認証の結果をアクセスログに残すと、どのリクエストが何で拒否されたかを追えます。log_format は http ブロックに書きます。

# http ブロック内
log_format auth_log '$remote_addr [$time_local] "$request" $status auth=$auth_status user=$auth_user';

# server または location ブロック内
auth_request_set $auth_status $upstream_status;
access_log /var/log/nginx/auth.log auth_log;

認証サーバーが止まっているときは、サブリクエストが失敗して 500 が返り、error_log に接続エラーが記録されます。全リクエストが認証サーバーを経由するので、認証サーバーの応答時間はそのままサイトの表示速度に効きます。

まとめ

  • auth_request はサブリクエストの応答が 2xx なら許可、401・403 なら拒否、それ以外は 500 にする。
  • モジュールの有無は nginx -V で確認する。公式パッケージや主なディストリビューションのパッケージには多くの場合含まれ、NGINX Plus は不要。
  • WordPress の管理画面だけを守るときは location ^~ /wp-admin/ の中に PHP の location を入れ子にする。\.php$ 全体に auth_request をかけるとサイト全体が認証対象になる。
  • 認証結果は auth_request_set で受け取り、PHP には HTTP_ で始まらない名前で渡す。
  • パスだけの保護は ?rest_route= などで回避されることがあるので、WordPress 側の制御と組み合わせる。

Nginx で WordPress を動かす基本の設定は、NginxでWordPressを動かす設定で説明しています。

お気軽にご相談ください

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