冒頭まとめ

Nginx の 403 Forbidden は、サーバーリクエストを理解したうえで、アクセスを拒否したときに返されます。原因はほぼ次の6つのいずれかです。ファイル権限の不足、パスの途中の親ディレクトリに実行権限がない、index ファイルがなく autoindex も無効、設定の deny ルール、SELinux/AppArmor、そして upstream(PHP-FPM など)自身が 403 を返すケースです。調査は、設定をいじる前に、まず /var/log/nginx/error.log を読むことから始めます。ログの文言が、どの原因なのかの手がかりになります。

エラーの概要

403 Forbidden は、Nginx がリクエスト自体は正しく受け取り、対象のリソースの場所も分かっているが、アクセスを拒否した状態です。認証情報が足りない 401 Unauthorized とは異なり、再認証しても解決しません。リソースが存在しない 404 Not Found とも異なります。

この違いはエラーログで明確に区別できます。404 はログNo such file or directory と記録され、403 は Permission deniedis forbidden と記録されます。アクセスログは「403 が起きた」ことだけを示し、なぜ起きたかはエラーログが示します。したがって、403 の調査は設定ファイルをいじる前に、まずエラーログを読むことから始めます。

まず最初に:エラーログを読む

原因を切り分ける前に、エラーログの該当行を確認します。

# 直近のエラーを表示
sudo tail -50 /var/log/nginx/error.log

# 権限・拒否に関する行だけを抽出
sudo grep -iE "permission denied|forbidden|denied" /var/log/nginx/error.log

ログの文言と原因の対応は次のとおりです。

# ファイルまたは親ディレクトリの権限不足
open() "/var/www/html/index.html" failed (13: Permission denied)

# index ファイルがなく autoindex も無効
directory index of "/var/www/html/" is forbidden

# upstream への接続が拒否された(SELinux でソケット接続が遮られた例)
connect() to 127.0.0.1:8080 failed (13: Permission denied) while connecting to upstream

この文言で、おおよその原因の見当がつきます。以下、6つの原因を、切り分けるべき順に説明します。

よくある原因と解決手順

原因1:ファイルまたはディレクトリの権限が不足している

最も多い原因です。Nginx のワーカープロセスは、配信するファイルに読み取り権限が必要です。エラーログには (13: Permission denied) が出ます。

まず Nginx のワーカーユーザーを確認します。ディストリビューションによって異なります。

# ワーカープロセスのユーザーを確認
ps -o user,comm -C nginx
# root     nginx   ← master プロセス
# www-data nginx   ← worker プロセス(こちらが実際にファイルを読む)

# 設定上のユーザー指定を確認
grep -E "^\s*user" /etc/nginx/nginx.conf
# Ubuntu/Debian は www-data、CentOS/RHEL は nginx、Alpine は nginx が既定

権限を、ディレクトリ 755・ファイル 644 に揃え、所有者をワーカーユーザーにします。

# 所有者をワーカーユーザーに設定
sudo chown -R www-data:www-data /var/www/html

# ディレクトリは 755、ファイルは 644 に揃える
sudo find /var/www/html -type d -exec chmod 755 {} \;
sudo find /var/www/html -type f -exec chmod 644 {} \;

# 設定の文法確認とリロード
sudo nginx -t
sudo systemctl reload nginx

# 確認
curl -I http://localhost/
# HTTP/1.1 200 OK が返れば解決

原因2:パスの途中の親ディレクトリに実行権限がない

ファイル自体が 644 で正しくても、ルート(/)から対象ファイルまでのパス上のどこかのディレクトリに、ワーカーユーザーの実行(検索)権限がないと、Nginx はそこを通り抜けられず 403 になります。見落としやすい典型例です。

namei -l を使うと、パスの各構成要素の権限を一覧でき、どこで止まっているかが分かります。ワーカーユーザーとして実行するのが確実です。

# パス全体の権限をワーカーユーザーの視点で確認
sudo -u www-data namei -l /var/www/site/index.html
# f: /var/www/site/index.html
# drwxr-xr-x root     root     /
# drwxr-xr-x root     root     var
# drwxr-xr-x root     root     www
# drwxr-x--- deploy   deploy   site        ← ここが原因。other に x がない
# -rw-r--r-- deploy   deploy   index.html

上の例では /var/www/site が 750 で、所有者と所有グループしか入れず、www-data は通れません。対象ディレクトリに、ワーカーユーザーが通れる実行権限を与えます。

# パス上のディレクトリに検索(実行)権限を付与
sudo chmod o+x /var/www/site

# または、グループ所有を www-data にしてグループに権限を与える
sudo chgrp www-data /var/www/site
sudo chmod g+rx /var/www/site

ユーザーのホームディレクトリ配下を配信しようとして 403 になるのも、この原因です。ホームディレクトリの既定権限が制限的(700 など)で、Nginx が通り抜けられないためです。

原因3:index ファイルがなく、autoindex も無効

ディレクトリへのリクエストに対し、index ディレクティブで指定したファイル(既定では index.html)が存在せず、autoindex も off(既定)の場合、Nginx は 403 を返します。エラーログには directory index of "..." is forbidden が出ます。

index ファイルを置くか、ディレクトリ一覧を見せてよい場面なら autoindex を有効にします。

# 対処A:index ファイルを明示して配信する
server {
    listen 80;
    server_name example.com;
    root /var/www/html;

    location / {
        index index.html index.htm;
        try_files $uri $uri/ =404;
    }
}
# 対処B:ディレクトリ一覧を見せてよい場合のみ autoindex を有効化
location /downloads/ {
    autoindex on;
    autoindex_exact_size off;   # サイズを読みやすい単位で表示
    autoindex_localtime on;     # ローカル時刻で表示
}

autoindex の有効化は、ディレクトリの中身を外部に晒すことになります。公開サーバーでは、見せてよいディレクトリに限定してください。

原因4:設定の deny ルールや auth_basic で拒否されている

deny ディレクティブ、auth_basicinternal などの設定が、リクエストを意図的に拒否しているケースです。設定全体を展開して、該当する行を探します。

# 展開後の設定全体から、アクセス制御に関わる行を探す
sudo nginx -T | grep -nE "deny|allow|auth_basic|internal"

たとえば次の設定は、特定 IP 以外を拒否します。

location /admin/ {
    allow 192.168.1.0/24;   # 社内ネットワークのみ許可
    deny all;               # それ以外は拒否
}

意図した制限ならそのままです。意図せず拒否している場合は、allow の範囲を見直すか、deny ルールを修正します。アクセス元の IP が想定どおりか、ログで確認してください。

原因5:SELinux または AppArmor がアクセスを遮っている

Unix の権限が正しくても、強制アクセス制御(SELinux は RHEL/CentOS 系、AppArmor は Ubuntu 系)が、その下の層でアクセスを拒否することがあります。権限を直しても 403 が消えない場合に疑います。

SELinux(RHEL/CentOS 系)の確認と対処です。

# SELinux が有効か確認
getenforce
# Enforcing なら有効

# Nginx に関する拒否ログを確認
sudo ausearch -m avc -ts recent | grep nginx

# 現在のセキュリティコンテキストを確認
ls -Z /var/www/html/

# Web コンテンツ用の正しいコンテキストを付与する
sudo semanage fcontext -a -t httpd_sys_content_t "/var/www/html(/.*)?"
sudo restorecon -Rv /var/www/html/

SELinux を無効化(setenforce 0)して解決するのは避けてください。原因のコンテキストを正すのが正しい対処です。

AppArmor(Ubuntu 系)の確認です。

# AppArmor の拒否がカーネルログに出ていないか確認
sudo dmesg | grep -i apparmor

# Nginx のプロファイル状態を確認
sudo aa-status | grep nginx

原因6:upstream(PHP-FPM やアプリサーバー)が 403 を返している

Nginx 自体ではなく、proxy_passfastcgi_pass の先(PHP-FPM やアプリケーションサーバー)が 403 を返し、それがそのままクライアントに伝わるケースです。この場合、Nginx の権限やコンテキストは正常で、原因は upstream 側にあります。

upstream 側のログを確認し、ソケットの所有権を点検します。

# PHP-FPM のログを確認
sudo tail -50 /var/log/php*-fpm.log

# FastCGI ソケットの所有権を確認(Nginx ワーカーが接続できるか)
ls -l /run/php/php-fpm.sock

Nginx のエラーログ権限の文言がなく、upstream への接続自体は成功しているのに 403 が返る場合は、この原因を疑います。

切り分けの順序

403 は、次の順で原因を一つずつ除外していくと、最短で特定できます。

エラーログを読む。Permission denied なら原因1か2、directory index ... is forbidden なら原因3。権限を点検する(namei -l で親ディレクトリの実行権限まで)。設定を点検する(nginx -T | grep で deny/auth_basic を確認)。権限も設定も正しいのに消えないなら SELinux/AppArmor を疑う。それでも残るなら upstream のログを見る。この順に進めれば、6つのどれかに必ず行き着きます。

それでも解決しない場合

各段階で使う確認コマンドをまとめます。

# 1. エラーログの該当行
sudo grep -iE "permission denied|forbidden|denied" /var/log/nginx/error.log

# 2. ワーカーユーザーの視点でパス全体の権限を確認
sudo -u www-data namei -l /var/www/html/index.html

# 3. 設定中のアクセス制御を確認
sudo nginx -T | grep -nE "deny|allow|auth_basic|internal"

# 4. SELinux のコンテキストと拒否ログ
ls -Z /var/www/html/
sudo ausearch -m avc -ts recent | grep nginx

# 5. 設定の文法確認とリロード
sudo nginx -t && sudo systemctl reload nginx

Editor’s Note

実際の報告例として、ユーザーのホームディレクトリ配下を Nginx で配信しようとして 403 になった事例があります(GitHub gist の議論)。なお、この議論は2013年頃の古いもので、当時の Ubuntu 12.04 を前提としています。ただし、ここで扱われているパス権限の考え方は、現在の Nginx でも変わりません。報告者の環境では、ホームディレクトリの既定権限が制限的(700)で、Nginx のワーカーユーザーが親ディレクトリを通り抜けられず 403 になっていました。本記事の原因2にあたります。この議論では、配信対象のファイルすべてに読み取り権限を与えるだけでなく、ルートから対象までのパス上のすべての親ディレクトリに実行(検索)権限が必要だ、という点が解決の核心として挙げられています。ディレクトリ 755・ファイル 644 に揃える対処が広く有効だと共有されています。

この事例が示すとおり、403 の調査では「ファイル単体の権限」だけでなく「パス全体の通り抜け可否」を確認することが重要です。namei -lパスの各段を一度に点検するのが、確実な近道です。


免責事項:本記事の内容は、執筆時点の公開情報をもとに作成したものです。ソフトウェアの仕様は予告なく変更されることがあります。最新の情報は各ツールの公式サポートページをご確認ください。本記事の情報を利用した結果生じたいかなる損害についても、著者および運営者は責任を負いかねます。