nginxのBasic認証は、①htpasswd などで「ユーザー名:ハッシュ化したパスワード」のファイルを作り、②設定ファイルに auth_basic と auth_basic_user_file の2行を書き、③nginx -t で確認して再読み込みする、という3ステップで設定できます。 ユーザー名とパスワードを知っている人だけがページを開けるようになる、もっとも手軽なアクセス制限です。

この記事では、htpasswd(apache2-utils)と openssl passwd の両方でパスワードファイルを作る方法から、ハッシュ方式(bcrypt・apr1・SHA など)ごとに nginx が受け付けるかどうか、curl -u での 401/200 の確認、権限の不足やパスの誤りで実際に出る 403/500 と error.log の内容、satisfy any で特定IPだけ認証を省く設定、/health など一部パスの除外、リバースプロキシ配下での扱い、HTTPSが必要な理由までを扱います。すべて nginx 1.24.0(Ubuntu 24.04)で専用のインスタンスを起動し、curl で応答を確かめた結果を載せています。

nginx Basic認証の早見表

設定に使うディレクティブ

ディレクティブ 書き方 意味 書ける場所
auth_basic auth_basic "Members Only"; 認証を有効にする。文字列は認証ダイアログに表示される領域名(realm) http / server / location / limit_except
auth_basic auth_basic off; 上位で有効にした認証を、その範囲だけ無効にする 同上
auth_basic_user_file auth_basic_user_file /etc/nginx/.htpasswd; ユーザー名とパスワードのファイルを指定 同上
satisfy satisfy any; IP制限と認証の「どちらかを満たせば許可」にする(既定は all=両方必要) http / server / location
allow / deny allow 192.0.2.0/24; deny all; 接続元IPで許可・拒否する http / server / location / limit_except

コマンド逆引き

やりたいこと コマンド
ファイルを新規作成して1人目を登録 sudo htpasswd -c /etc/nginx/.htpasswd tanaka
2人目以降を追加(-c は付けない) sudo htpasswd /etc/nginx/.htpasswd suzuki
bcrypt で登録 sudo htpasswd -B /etc/nginx/.htpasswd tanaka
パスワードを変更 sudo htpasswd /etc/nginx/.htpasswd tanaka(同じユーザー名で上書き)
ユーザーを削除 sudo htpasswd -D /etc/nginx/.htpasswd suzuki
パスワードが合っているか確認 htpasswd -v /etc/nginx/.htpasswd tanaka
htpasswd を使わずに作成 printf 'tanaka:%s\n' "$(openssl passwd -apr1)" | sudo tee -a /etc/nginx/.htpasswd
設定の文法チェック sudo nginx -t
設定の再読み込み sudo systemctl reload nginx
認証なしで確認(401 になる) curl -i http://example.com/
認証ありで確認(200 になる) curl -i -u tanaka:パスワード http://example.com/

応答コードと原因

応答 主な原因 error.log に出るメッセージ
401 認証情報なし/パスワード違い/ユーザーなし/ファイルの書式が違う no user/password was provided、password mismatch、was not found in
403 パスワードファイルが見つからない(パスの誤り) open() "..." failed (2: No such file or directory)
500 パスワードファイルを読む権限がない open() "..." failed (13: Permission denied)
403 satisfy all で IP 制限に該当 access forbidden by rule

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「nginx Basic認証の設定手順|htpasswd作成からエラー対処まで」

Basic認証の仕組みを1分で

Basic認証は、HTTP の仕様で定められた認証方式です。流れは次のとおりです。

sequenceDiagram
    participant B as ブラウザー
    participant N as nginx
    B->>N: GET /(認証情報なし)
    N-->>B: 401 Unauthorized + WWW-Authenticate: Basic realm="Members Only"
    Note over B: ユーザー名とパスワードの入力ダイアログを表示
    B->>N: GET / + Authorization: Basic dGFuYWthOi4uLg==
    Note over N: パスワードファイルと照合
    N-->>B: 200 OK
用語 意味
realm(レルム) 認証の領域名。auth_basic "..." に書いた文字列で、ダイアログに表示される
WWW-Authenticate ヘッダー サーバーが「認証が必要です」と伝えるレスポンスヘッダー
Authorization ヘッダー ブラウザーが「ユーザー名:パスワード」を Base64 に変換して送るリクエストヘッダー
パスワードファイル 「ユーザー名:ハッシュ」を1行ずつ書いたテキスト。慣例で .htpasswd という名前を使う

ブラウザーは一度認証に通ると、以降のリクエストすべてに Authorization ヘッダーを付けて送ります。 nginx はリクエストのたびにパスワードファイルと照合します。この2点が、後半で説明する「HTTPSが必要な理由」と「ハッシュ方式の選び方」に関わってきます。

手順1:パスワードファイルを作る

htpasswd で作る(apache2-utils)

htpasswd は Apache HTTP Server に付属するコマンドで、nginx 用のファイル作成にもそのまま使えます。

OS インストール
Ubuntu / Debian sudo apt install apache2-utils
RHEL 系(AlmaLinux・Rocky Linux など) sudo dnf install httpd-tools

ここでは apache2-utils 2.4.58(Ubuntu 24.04)で実行しました。

# 新規作成(-c)して tanaka を登録。パスワードは対話で2回入力
sudo htpasswd -c /etc/nginx/.htpasswd tanaka

# 2人目を追加(-c を付けない)
sudo htpasswd /etc/nginx/.htpasswd suzuki

検証では、スクリプトで再現できるよう -b(パスワードを引数で渡す)を使いました。

htpasswd -c -b conf/.htpasswd tanaka 'S3cret-pass'
htpasswd -b conf/.htpasswd suzuki 'An0ther-pass'
cat conf/.htpasswd

実行結果:

Adding password for user tanaka
Adding password for user suzuki
tanaka:$apr1$LAG4YJ4R$DmLfchXfZafKVdYlTgr7E/
suzuki:$apr1$/T.Uq5vE$zHv4jO/8DnwVXEIfhHULo/

1行が1ユーザーで、「ユーザー名:ハッシュ」の形です。$apr1$ で始まるのは、Apache 版の MD5 ベース方式(apr1)で、htpasswd の既定です。

オプション 意味 補足
-c ファイルを新規作成 既存ファイルに付けると中身が置き換わる。 2人目以降は付けない
-b パスワードをコマンドの引数で渡す シェルの履歴に残るため、自動化スクリプト向け
-i パスワードを標準入力から読む echo 'pass' | htpasswd -i ファイル ユーザー
-n ファイルに書かず、結果を画面に表示 別の場所に貼り付けたいとき
-B bcrypt を使う -C でコスト(4〜17、既定5)を指定
-m apr1(MD5 ベース)を使う 既定
-s SHA-1 を使う ソルトなし。ヘルプでも他方式が案内されている
-D ユーザーを削除
-v パスワードが合っているか確認

確認と削除の動作も実行しました。

htpasswd -v -b conf/.htpasswd tanaka 'S3cret-pass'
htpasswd -v -b conf/.htpasswd tanaka 'wrong'; echo "exit=$?"
htpasswd -D conf/.htpasswd suzuki
Password for user tanaka correct.
password verification failed
exit=3
Deleting password for user suzuki

openssl passwd で作る(htpasswd がない環境)

htpasswd を入れられない環境でも、openssl があれば同じ形式のハッシュを作れます(OpenSSL 3.0.13 で実行)。

# 対話でパスワードを入力して1行追加(apr1)
printf 'tanaka:%s\n' "$(openssl passwd -apr1)" | sudo tee -a /etc/nginx/.htpasswd

# SHA-512 ベースの crypt 形式で追加
printf 'tanaka:%s\n' "$(openssl passwd -6)" | sudo tee -a /etc/nginx/.htpasswd

検証で実行したコマンドと結果です(パスワードを引数で渡しています)。

printf 'tanaka:%s\n' "$(openssl passwd -apr1 'S3cret-pass')"
printf 'tanaka:%s\n' "$(openssl passwd -6 'S3cret-pass')"
printf 'tanaka:%s\n' "$(openssl passwd -5 'S3cret-pass')"
printf 'tanaka:%s\n' "$(openssl passwd -1 'S3cret-pass')"
tanaka:$apr1$WjKGMRUg$01PE/p0HrXoKDa9CLlX9K1
tanaka:$6$6wqjG9gTLE62GXEN$XY.LgmGomTezKGZOgy9i9L3ySZp7d3MWwtU5xxtkojuxQrVDWTg0Hx.20c0ulMZk/Z/FX89LwyYKgArwVCZlX.
tanaka:$5$v087HVCw.F6lxUWg$AVJ2O7WYLlxcg2sZBBTuw8iXq.WFLJnNe4.p2lyU1/C
tanaka:$1$g.YfLUfy$0ebm9MUkctmBLT0PynayZ0
openssl passwd のオプション 方式 先頭の文字列
-apr1 Apache 版 MD5(htpasswd の既定と同じ) $apr1$
-6 SHA-512 ベースの crypt $6$
-5 SHA-256 ベースの crypt $5$
-1 MD5 ベースの crypt $1$

openssl passwd はハッシュ部分だけを出力するので、printf 'ユーザー名:%s\n' でユーザー名を前に付けて1行にします。tee -a の -a は追記の指定で、付けないとファイルが置き換わります。

ファイルの置き場所と権限

項目 おすすめ 理由
置き場所 /etc/nginx/.htpasswd など、公開ディレクトリ(root)の外 公開ディレクトリに置くと、ファイル自体がダウンロードできてしまう
所有者とグループ root:www-data(nginx の実行ユーザーのグループ) 管理者だけが書き換えられ、nginx は読める
パーミッション 640 所有者は読み書き、グループは読み取りのみ、その他は不可
sudo chown root:www-data /etc/nginx/.htpasswd
sudo chmod 640 /etc/nginx/.htpasswd

nginx の実行ユーザーは、nginx.conf の user ディレクティブで確認できます(Ubuntu / Debian のパッケージは www-data、nginx.org 公式パッケージや RHEL 系は nginx)。検証環境では次のように、マスタープロセスが root、リクエストを処理するワーカープロセスが www-data で動いていました。

USER       PID CMD
root      9751 nginx: master process nginx -c /tmp/ngx-auth-demo/conf/nginx.conf -p /tmp/ngx-auth-demo
www-data  9752 nginx: worker process

パスワードファイルを読むのはワーカープロセス(www-data)です。root では読めても www-data で読めない権限になっていると、後述の 500 になります。

公開ディレクトリに置いた場合の動きも確認しました。.htpasswd を root で指定したディレクトリにコピーし、認証を通したうえで取得すると、応答は 200 でファイルの中身が返りました。置き場所は公開ディレクトリの外にするのが確実です。

手順2:nginx に設定を書く

サイト全体に認証をかける

server {
    listen 80;
    server_name example.com;
    root /var/www/html;
    index index.html;

    # サイト全体に Basic 認証
    auth_basic           "Members Only";
    auth_basic_user_file /etc/nginx/.htpasswd;

    location / { }
}

特定のディレクトリだけに認証をかける

server {
    listen 80;
    server_name example.com;
    root /var/www/html;

    location / { }

    # /admin/ 以下だけ認証
    location /admin/ {
        auth_basic           "Admin Area";
        auth_basic_user_file /etc/nginx/.htpasswd;
    }
}
書く場所 認証がかかる範囲
http { } そのnginxが扱うすべてのサイト
server { } そのサイト全体
location { } そのパスに一致するリクエストだけ

auth_basic は上位の設定を引き継ぎます。 server に書くと、その中のすべての location に適用されます。一部だけ外したいときは、その location に auth_basic off; を書きます(後述)。

auth_basic の文字列(realm)は、ブラウザーの認証ダイアログに表示されます。英数字で書いておくと、ブラウザーを問わず同じように表示されます。

設定の確認と再読み込み

sudo nginx -t
sudo systemctl reload nginx

nginx -t の結果(検証環境):

nginx: the configuration file /tmp/ngx-auth-demo/conf/nginx.conf syntax is ok
nginx: configuration file /tmp/ngx-auth-demo/conf/nginx.conf test is successful

nginx -t は文法を確認するコマンドで、パスワードファイルが存在するか・読めるかまでは確認しません。 後述のとおり、パスを間違えたままでも test is successful になり、リクエストが来て初めてエラーになります。そのため、次の curl での確認が大切です。

手順3:curl で 401 と 200 を確認する

検証は、他のサービスに影響しないよう、/tmp/ngx-auth-demo/ 以下に設定とファイルを置いた専用の nginx 1.24.0 を 127.0.0.1:7460 で起動して行いました。以降の実行結果に出てくるパスとポートはこの検証環境のものです。

認証情報なし → 401

curl -si http://127.0.0.1:7460/ | head -8
HTTP/1.1 401 Unauthorized
Server: nginx/1.24.0 (Ubuntu)
Date: Fri, 02 Oct 2026 00:39:31 GMT
Content-Type: text/html
Content-Length: 188
Connection: keep-alive
WWW-Authenticate: Basic realm="Members Only"

WWW-Authenticate: Basic realm="Members Only" が返っていれば、認証が有効になっています。

正しい認証情報 → 200

curl -si -u tanaka:S3cret-pass http://127.0.0.1:7460/ | head -1
curl -s  -u tanaka:S3cret-pass http://127.0.0.1:7460/
HTTP/1.1 200 OK
<h1>member page</h1>

確認方法の一覧

方法 コマンド 結果
-u オプション curl -u tanaka:S3cret-pass http://127.0.0.1:7460/ 200
URL に埋め込む curl http://tanaka:S3cret-pass@127.0.0.1:7460/ 200
ヘッダーを直接指定 curl -H 'Authorization: Basic dGFuYWthOlMzY3JldC1wYXNz' http://127.0.0.1:7460/ 200
パスワード違い curl -u tanaka:wrong http://127.0.0.1:7460/ 401
存在しないユーザー curl -u nobody:x http://127.0.0.1:7460/ 401

ステータスコードだけを見たいときは、curl -s -o /dev/null -w '%{http_code}\n' を付けると数字だけが表示されます。

ログでの見え方

access.log には、認証に通ったユーザー名が3番目の項目($remote_user)として記録されます。

127.0.0.1 - - [02/Oct/2026:09:39:31 +0900] "GET / HTTP/1.1" 401 188 "-" "curl/8.5.0"
127.0.0.1 - tanaka [02/Oct/2026:09:39:31 +0900] "GET / HTTP/1.1" 200 21 "-" "curl/8.5.0"
127.0.0.1 - tanaka [02/Oct/2026:09:39:31 +0900] "GET / HTTP/1.1" 401 188 "-" "curl/8.5.0"

1行目は認証情報なし(-)の 401、2行目は tanaka で成功した 200、3行目は tanaka のパスワード違いによる 401 です。401 の行にもユーザー名が出るため、どのユーザー名で試行が続いているかを把握できます。

ユーザーの追加・削除は再読み込みなしで反映される

nginx を動かしたまま htpasswd でユーザーを追加・削除し、直後に curl で確認しました。

操作 直後の curl -u yamada:New-pass1 の応答
追加前 401
htpasswd -b conf/.htpasswd yamada 'New-pass1' の直後(reload なし) 200
htpasswd -D conf/.htpasswd yamada の直後(reload なし) 401

パスワードファイルはリクエストのたびに読み込まれるため、ユーザーの追加・変更・削除に nginx の再読み込みは不要です。再読み込みが必要なのは、nginx.conf 側(auth_basic やファイルのパス)を変えたときです。

ハッシュ方式ごとの対応状況

パスワードファイルに書けるハッシュ方式は複数あります。nginx の公式ドキュメントでは、次の3種類が挙げられています。

  • crypt() 関数で暗号化したもの(htpasswd や openssl passwd で作成できる)
  • Apache 版の MD5 ベース方式(apr1)
  • {scheme}data の形式で書くもの(PLAIN・SHA・SSHA)。公式ドキュメントには、PLAIN は例示用、SHA は使用を避けるよう注記があります

方式ごとにファイルを作り、同じパスワードで curl -u を実行して応答を確かめました(nginx 1.24.0/Ubuntu 24.04)。

方式 作り方 ファイル内の形 応答 補足
apr1 htpasswd -m(既定)/openssl passwd -apr1 $apr1$... 200 nginx 自身が処理。OS を問わず使える
bcrypt htpasswd -B $2y$05$... 200 OS の crypt() 経由。OS が対応している場合に使える
SHA-512 crypt openssl passwd -6 $6$... 200 OS の crypt() 経由
SHA-256 crypt openssl passwd -5 $5$... 200 OS の crypt() 経由
MD5 crypt openssl passwd -1 $1$... 200 OS の crypt() 経由
SHA-1 htpasswd -s {SHA}nZHAfCZt... 200 ソルトなし。公式ドキュメントで使用を避けるよう注記
従来型 crypt(DES) htpasswd -d zlVW.AQodGld2 200 パスワードが8文字までで切り詰められる
平文 手書き {PLAIN}S3cret-pass 200 公式ドキュメントで例示用とされている

htpasswd -d を実行すると、次の警告が表示されました。9文字目以降が無視されるため、長いパスワードを設定しても先頭8文字だけで認証に通ることになります。

Warning: Password truncated to 8 characters by CRYPT algorithm.

どの方式を選ぶか

状況 選ぶ方式 理由
まず確実に動かしたい・複数の OS で同じファイルを使う apr1(htpasswd の既定) nginx 自身が処理するため、OS の違いに左右されない
総当たりへの耐性を高めたい bcrypt(htpasswd -B)または SHA-512 crypt(openssl passwd -6) 計算に時間がかかる方式で、ファイルが外に出た場合の解析に時間がかかる
避けたい方式 {SHA}・従来型 crypt・{PLAIN} ソルトなし/8文字まで/平文、という性質がある

bcrypt と $5$・$6$ は、nginx が OS の crypt() 関数に処理を任せる方式です。今回の Ubuntu 24.04 ではすべて 200 になりましたが、OS やコンテナイメージによって crypt() が対応する方式は異なります。別の環境へ持っていくときは、切り替えた直後に curl -u で 200 になることを確認すると確実です。OS が対応していない方式で書いた行は、正しいパスワードでも認証に通らない可能性があります。

bcrypt のコスト(-C)は、数字を1上げるごとに計算量が2倍になります。Basic認証はリクエストのたびにパスワードを照合するため、コストを大きくすると、画像やCSSを含むすべてのリクエストでその計算が行われます。htpasswd の既定はコスト5です。アクセス数の多いページで使う場合は、応答時間を見ながら決めるとよいでしょう。

403・500・401 の原因を error.log で切り分ける

うまく動かないときは、error.log を見るのが近道です。よくあるミスを意図的に作り、応答コードと error.log の内容を記録しました。

# error.log を追いかけながら、別の端末で curl を実行する
sudo tail -f /var/log/nginx/error.log

一覧(実際に出たメッセージ)

状況 応答 ログレベル error.log のメッセージ(要点)
認証情報なしでアクセス 401 info no user/password was provided for basic authentication
パスワードが違う 401 error user "tanaka": password mismatch
ユーザーが登録されていない 401 error user "nobody" was not found in "/tmp/ngx-auth-demo/conf/.htpasswd"
ファイルの書式が違う(: で区切っていない) 401 error user "tanaka" was not found in ".../.htpasswd_broken"
ファイルのパスが間違っている 403 error open() ".../.htpasswd_typo" failed (2: No such file or directory)
相対パスで書いた結果、想定と別の場所を見ている 403 error open() "/tmp/ngx-auth-demo/conf/conf.d/.htpasswd" failed (2: No such file or directory)
ファイルを読む権限がない 500 crit open() ".../.htpasswd_noperm" failed (13: Permission denied)

403:ファイルが見つからない

auth_basic_user_file に、存在しないファイル名を指定した場合です。

location /nofile/ {
    auth_basic_user_file /tmp/ngx-auth-demo/conf/.htpasswd_typo;
}

nginx -t は成功し、正しいユーザー名とパスワードを付けても 403 になりました。error.log の内容です。

2026/10/02 09:39:31 [error] 9752#9752: *8 open() "/tmp/ngx-auth-demo/conf/.htpasswd_typo" failed (2: No such file or directory), client: 127.0.0.1, server: , request: "GET /nofile/ HTTP/1.1", host: "127.0.0.1:7460"

open() の直後に、nginx が実際に開こうとしたパスが表示されます。 このパスを ls -l で確認すると、つづりの違いや置き場所の違いに気づけます。

403:相対パスは nginx.conf のある場所が基準になる

auth_basic_user_file conf.d/.htpasswd; のように相対パスで書いた場合、nginx は設定ファイル(nginx.conf)のあるディレクトリを基準にパスを組み立てます。検証環境では nginx.conf が /tmp/ngx-auth-demo/conf/ にあったため、次の場所を探しにいきました。

2026/10/02 09:39:31 [error] 9752#9752: *10 open() "/tmp/ngx-auth-demo/conf/conf.d/.htpasswd" failed (2: No such file or directory), client: 127.0.0.1, server: , request: "GET /relative/ HTTP/1.1", host: "127.0.0.1:7460"

sites-available/ や conf.d/ に置いた設定ファイルの場所が基準になるわけではない点に注意が必要です。/etc/nginx/.htpasswd のように絶対パスで書くと、どこを見ているかが明確になります。

500:ファイルを読む権限がない

パスワードファイルの所有者を root、パーミッションを 600 にした場合です。

ls -l conf/.htpasswd_noperm
# -rw------- 1 root root 90 ... conf/.htpasswd_noperm

正しいユーザー名とパスワードを付けても 500 Internal Server Error になり、error.log には [crit] レベルで次の行が出ました。

2026/10/02 09:39:31 [crit] 9752#9752: *9 open() "/tmp/ngx-auth-demo/conf/.htpasswd_noperm" failed (13: Permission denied), client: 127.0.0.1, server: , request: "GET /noperm/ HTTP/1.1", host: "127.0.0.1:7460"

ワーカープロセス(www-data)がファイルを開けないことが原因です。グループを www-data に変え、640 にして直しました。

sudo chown root:www-data conf/.htpasswd_noperm
sudo chmod 640 conf/.htpasswd_noperm
ls -l conf/.htpasswd_noperm
# -rw-r----- 1 root www-data 90 ... conf/.htpasswd_noperm

変更後は nginx の再読み込みなしで 500 が解消し、認証を通過しました(検証用のパスにはコンテンツを置いていないため、応答は 404 になりました)。

ファイル自体の権限が正しくても、途中のディレクトリに実行権限(x)がないと同じ Permission denied になります。 次のコマンドで、nginx の実行ユーザーとして読めるかどうかを直接確かめられます。

sudo -u www-data test -r /etc/nginx/.htpasswd && echo "読める" || echo "読めない"

SELinux が有効な環境(RHEL 系)では、パーミッションが正しくても SELinux のラベルによって読み取りが止められることがあります。その場合は /etc/nginx/ 以下など nginx 用のラベルが付く場所に置くと解消しやすくなります(この点は今回の環境では再現しておらず、一般的な対処として記載しています)。

401:正しいはずのパスワードで通らない

確認すること 方法
ファイルに登録されているか sudo cat /etc/nginx/.htpasswd でユーザー名を確認
パスワードが合っているか htpasswd -v /etc/nginx/.htpasswd tanaka
1行が「ユーザー名:ハッシュ」になっているか 区切りが空白だったり改行が抜けていたりすると was not found in になる
-c で他のユーザーを消していないか -c は新規作成。既存ファイルに使うと1人だけになる
nginx が見ているファイルは想定どおりか error.log の was not found in "..." のパスを確認
ハッシュ方式に OS が対応しているか apr1 で作り直して通るか試す

書式を誤った例として、tanaka S3cret-pass(区切りが空白)という1行だけのファイルを指定したところ、応答は 401、error.log は user "tanaka" was not found in "..." でした。ファイルの書式が違う場合も「ユーザーが見つからない」と同じメッセージになるので、このメッセージが出たらファイルの中身も確認します。

特定のIPだけ認証を省く(satisfy any + allow)

「社内ネットワークからは認証なし、それ以外からはパスワードを求める」という設定は、satisfy any で書けます。

location /internal/ {
    satisfy any;              # IP 制限と Basic 認証の「どちらか」を満たせば許可

    allow 192.0.2.0/24;       # このネットワークからは認証なしで許可
    deny  all;                # それ以外は IP 制限では不許可(→ 認証へ)

    auth_basic           "Internal";
    auth_basic_user_file /etc/nginx/.htpasswd;
}

接続元IPを変えて確認するため、検証では realip モジュール(set_real_ip_from 127.0.0.1; と real_ip_header X-Forwarded-For;)を使い、X-Forwarded-For ヘッダーで接続元を切り替えました。

接続元IP 認証情報 satisfy any の応答 satisfy all の応答
192.0.2.10(許可リスト内) なし 200 401
192.0.2.10(許可リスト内) あり 200 200
203.0.113.9(許可リスト外) なし 401 403
203.0.113.9(許可リスト外) あり 200 403
指定 意味 使いどころ
satisfy all;(既定) IP 制限と認証の両方を満たす必要がある 「特定の拠点から、かつパスワードを知っている人だけ」
satisfy any; IP 制限と認証のどちらかを満たせばよい 「特定の拠点からは認証なし、外からはパスワード」

satisfy を書かずに allow/deny と auth_basic を並べると、既定の all が働き、許可リスト外のIPは正しいパスワードを入れても 403 になります。そのときの error.log は次のとおりでした。

2026/10/02 09:39:31 [error] 9752#9752: *32 access forbidden by rule, client: 203.0.113.9, server: , request: "GET /both/ HTTP/1.1", host: "127.0.0.1:7462"

ロードバランサーや CDN の後ろに nginx がある構成では、nginx から見た接続元IPがロードバランサーのIPになります。 allow に書くIPで判定したい場合は、realip モジュールで本来の接続元IPを復元する設定(set_real_ip_from に信頼できる中継サーバーのIPだけを指定)を合わせて行います。

一部のパスだけ認証を外す(auth_basic off)

サイト全体に認証をかけつつ、死活監視用の /health や証明書更新用のパスだけ外したい場合は、その location に auth_basic off; を書きます。

server {
    listen 80;
    server_name example.com;
    root /var/www/html;

    auth_basic           "Members Only";
    auth_basic_user_file /etc/nginx/.htpasswd;

    location / { }

    # 死活監視用:認証なし
    location = /health {
        auth_basic off;
        return 200 "ok\n";
    }

    # Let's Encrypt の HTTP-01 チャレンジ用:認証なし
    location ^~ /.well-known/acme-challenge/ {
        auth_basic off;
    }
}

確認結果です。

curl -si http://127.0.0.1:7460/health | head -1
curl -si http://127.0.0.1:7460/ | head -1
HTTP/1.1 200 OK
HTTP/1.1 401 Unauthorized
認証を外すことが多いパス 理由
/health・/healthz など 監視ツールやロードバランサーは認証情報を送らないことが多い
/.well-known/acme-challenge/ 証明書の発行・更新で認証局が認証情報なしでアクセスする
Webhook の受け口 外部サービスが送ってくるリクエストに Basic認証を付けられない場合がある(別の検証方法と併用する)
/robots.txt・/favicon.ico 必要に応じて

location = /health の = は完全一致の指定です。location /health と書くと /healthcheck など前方一致するパスも対象になるため、外す範囲を1つのURLに絞りたいときは = を使います。

リバースプロキシ配下で使う

nginx をリバースプロキシとして使い、後ろのアプリケーションの手前で Basic認証をかける構成です。

location /app/ {
    auth_basic           "Members Only";
    auth_basic_user_file /etc/nginx/.htpasswd;

    proxy_pass http://127.0.0.1:3000/;

    # 認証に通ったユーザー名をバックエンドへ伝える
    proxy_set_header X-Remote-User $remote_user;

    # Authorization ヘッダーをバックエンドへ渡さない
    proxy_set_header Authorization "";
}

バックエンド役として、受け取ったヘッダーをそのまま返すサーバーを 127.0.0.1:7461 に用意して確かめました。

設定 バックエンドが受け取った内容
proxy_set_header Authorization ""; なし authorization=[Basic dGFuYWthOlMzY3JldC1wYXNz] x-remote-user=[tanaka]
proxy_set_header Authorization ""; あり authorization=[] x-remote-user=[tanaka]
認証情報なしでアクセス nginx が 401 を返し、バックエンドには届かない

既定では、Authorization ヘッダーはそのままバックエンドへ転送されます。 中身は Base64 に変換されたユーザー名とパスワードです。バックエンド側でこの値を使わないのであれば、proxy_set_header Authorization ""; で空にしておくと、アプリケーションのログなどにパスワード相当の情報が残ることを避けられます。

反対に、バックエンドが独自に Authorization: Bearer ... のようなトークン認証を使っている場合は注意が必要です。Authorization ヘッダーは1つしか送れないため、nginx の Basic認証とアプリケーションのトークン認証は同じヘッダーを取り合います。 API のパスだけ auth_basic off; にする、あるいは IP 制限に切り替える、といった分け方が現実的です。

ヘッダー・変数 内容
$remote_user 認証に通ったユーザー名。ログやヘッダーに使える
X-Remote-User(任意の名前) バックエンドへユーザー名を伝えるためのヘッダー。バックエンドは nginx 経由のリクエストだけを受ける構成にする

HTTPS が必要な理由

Basic認証では、ユーザー名とパスワードが Base64 という「変換」だけをかけた状態で、リクエストのたびに送られます。 Base64 は暗号化ではなく、誰でも元に戻せます。

curl -v で送信されるヘッダーを確認しました。

curl -sv -u tanaka:S3cret-pass http://127.0.0.1:7460/ 2>&1 | grep -i '^> Authorization'
> Authorization: Basic dGFuYWthOlMzY3JldC1wYXNz

この文字列を Base64 でデコードすると、元のユーザー名とパスワードがそのまま現れます。

echo 'dGFuYWthOlMzY3JldC1wYXNz' | base64 -d
tanaka:S3cret-pass

HTTP(暗号化なし)の通信では、経路上でこのヘッダーが読める状態で流れます。HTTPS にすると、ヘッダーを含む通信全体が暗号化されるため、Basic認証は HTTPS とセットで使うのが基本です。

# HTTP で来たリクエストは HTTPS へ転送(認証は HTTPS 側だけにかける)
server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    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;

    auth_basic           "Members Only";
    auth_basic_user_file /etc/nginx/.htpasswd;
}

検証では自己署名証明書を使った HTTPS のサーバー(127.0.0.1:7463)でも同じ設定を試し、curl --cacert demo.crt -u tanaka:S3cret-pass https://127.0.0.1:7463/ で 200 が返ることを確認しました。

HTTP 側の server はリダイレクトだけにし、認証ダイアログは HTTPS 側で出すのがポイントです。HTTP 側でコンテンツを返しながら認証をかけると、暗号化されていない通信でユーザー名とパスワードを送ることになります。

Basic認証の特徴と向いている用途

特徴 内容
設定の手軽さ 2行の設定とファイル1つで始められる
ログアウト ブラウザーを閉じるまで認証情報が保持される。ログアウト機能はない
試行回数の制限 標準では回数制限がない。limit_req などと組み合わせる
権限の細かさ 「通す・通さない」の2択。ユーザーごとの権限分けは別の仕組みが必要
向いている用途 別の方式が向いている用途
公開前の確認用サイト(ステージング)の閲覧制限 一般の利用者向けの会員ログイン
社内向けツールや管理画面の入口に鍵を1つ足す ユーザーごとに権限を分けたい画面
検索エンジンに載せたくない開発中のページ ログアウトやパスワード再設定が必要なサービス

連続した試行を抑える(limit_req)

limit_req を組み合わせると、短時間に繰り返されるリクエストを抑えられます。

http {
    limit_req_zone $binary_remote_addr zone=login:10m rate=5r/s;

    server {
        location /admin/ {
            limit_req zone=login burst=3 nodelay;
            limit_req_status 429;

            auth_basic           "Admin Area";
            auth_basic_user_file /etc/nginx/.htpasswd;
        }
    }
}

誤ったパスワードで8回続けてアクセスした結果です。

401 401 401 401 429 429 429 429

5回目以降は認証の照合まで進まず 429 が返り、error.log には limiting requests, excess: 3.905 by zone "login" と記録されました。rate と burst は、通常の利用(1ページの表示で画像やCSSなど複数のリクエストが発生する)を妨げない値に調整します。

設定前後のチェックリスト

確認項目 確認方法
パスワードファイルは公開ディレクトリの外にあるか auth_basic_user_file のパスと root を見比べる
nginx の実行ユーザーがファイルを読めるか sudo -u www-data test -r /etc/nginx/.htpasswd && echo OK
ファイルのパスは絶対パスか 設定ファイルを確認
文法に問題がないか sudo nginx -t
認証なしで 401 が返るか curl -i https://example.com/
認証ありで 200 が返るか curl -i -u ユーザー名:パスワード https://example.com/
認証を外したいパスは外れているか curl -i https://example.com/health
HTTP は HTTPS へ転送されるか curl -i http://example.com/ で 301 を確認
バックエンドへ Authorization を渡す必要があるか 不要なら proxy_set_header Authorization "";
error.log に想定外のメッセージがないか sudo tail /var/log/nginx/error.log

よくある質問

Q. htpasswd でユーザーを追加したあと、nginx の再起動は必要ですか? A. 不要です。パスワードファイルはリクエストのたびに読み込まれるため、追加・変更・削除は保存した直後から反映されます(検証でも reload なしで 401 から 200 に変わりました)。nginx.conf 側を変更したときは sudo nginx -t のあと sudo systemctl reload nginx を実行します。

Q. 正しいパスワードを入れても 403 や 500 になります。 A. error.log を確認してください。(2: No such file or directory) ならファイルのパスが違っており、応答は 403 になります。(13: Permission denied) なら nginx の実行ユーザーがファイルを読めておらず、応答は 500 になります。前者は絶対パスで書き直す、後者は chown root:www-data と chmod 640 で直せます。

Q. bcrypt(htpasswd -B)は nginx で使えますか? A. Ubuntu 24.04 の nginx 1.24.0 では 200 になり、使えることを確認しました。bcrypt は nginx が OS の crypt() 関数に処理を任せる方式のため、対応状況は OS によって異なります。切り替えたら curl -u で 200 が返ることを確認し、通らない場合は apr1 を使うと OS を問わず動きます。

Q. ブラウザーで毎回パスワードを聞かれない方法はありますか? A. ブラウザーは一度認証に通ると、閉じるまで同じサイトへ自動的に認証情報を送ります。決まったIPからのアクセスで入力自体を省きたい場合は、satisfy any と allow を組み合わせると、そのIPからは認証なしで開けるようになります。

まとめ

  • 設定は3ステップ:パスワードファイルを作る → auth_basic と auth_basic_user_file を書く → nginx -t と reload。 確認は curl -u で 401 と 200 を見る。
  • パスワードファイルは htpasswd(apache2-utils)でも openssl passwd でも作れる。 htpasswd -c は新規作成用で、2人目以降には付けない。
  • ハッシュ方式は apr1 が OS を問わず動き、bcrypt や $6$ は OS の crypt() が対応していれば使える。 切り替えたら curl で確認する。
  • 403 は「ファイルが見つからない」、500 は「読む権限がない」、401 は「認証情報が合わない・書式が違う」。 error.log の open() "..." に実際のパスが出る。
  • ファイルは公開ディレクトリの外に置き、root:www-data の 640 にする。 パスは絶対パスで書く。
  • satisfy any + allow で特定IPの認証を省き、auth_basic off で /health などを除外できる。
  • Basic認証は HTTPS とセットで使う。 Authorization ヘッダーは Base64 で、デコードすると元のパスワードが読める。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「nginx Basic認証の設定手順|htpasswd作成からエラー対処まで」

株式会社RJCでは、nginxの設定やアクセス制限の仕組みを仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?