403 Forbidden от nginx значит, что nginx нашёл путь, но отказывается его отдавать. Лог ошибок называет причину в одной строке: directory index of … is forbidden — нет индексного файла, (13: Permission denied) — права файловой системы или SELinux, а access forbidden by rule — сработало правило allow/deny. Сначала читайте лог, исправляйте эту одну причину и перезагружайте конфигурацию.
sudo tail -n 20 /var/log/nginx/error.log
# исправьте то, что показал лог, затем
sudo nginx -t && sudo systemctl reload nginxЧто говорит лог ошибок nginx?
/var/log/nginx/error.log — путь по умолчанию в Debian, Ubuntu, Rocky, Alma и Alpine. Конкретный сайт может писать ошибки в другое место своей строкой error_log (найти её — sudo grep -R error_log /etc/nginx/), а официальный образ Docker пишет в вывод контейнера, поэтому там нужен docker logs <container>. Перезагрузите страницу один раз и сверьтесь с самой новой строкой:
directory index of "/var/www/site/" is forbidden— причина 1, нет индексного файла.open() "/var/www/site/index.html" failed (13: Permission denied)— причина 2, или причина 3 на Rocky, Alma и RHEL.access forbidden by rule— причина 4, правило allow/deny.- Для этого запроса в логе вообще ничего нет — причина 5: 403, скорее всего, не от nginx.
Это тот же порядок действий «сначала лог», что и для 502 Bad Gateway; разница в том, что 403 почти всегда дело в файлах или конфигурации, а не в бэкенде.
Причина 1: есть ли индексный файл?
Запрос каталога — / или /blog/ — заставляет nginx искать файлы, указанные в директиве index, по умолчанию index.html. Если такого файла нет, а показ списков каталогов выключен (это по умолчанию), nginx отвечает 403, а не 404.
ls -la /var/www/site/ # есть ли index.html или index.php?
sudo nginx -T | grep -nE 'root|index|autoindex|try_files'rootуказывает не на ту папку. Фреймворки отдают файлы из подкаталога —public/для Laravel,dist/илиbuild/для сборки фронтенда. Направьтеrootтуда.- PHP-сайт без index.php в списке. Добавьте его первым:
index index.php index.html;. - Ожидание, что
try_files $uri $uri/ =404;вернёт 404. Для существующего каталога это не так:$uri/совпадает с ним, nginx ищет индексный файл, и без него получается 403. Решение — всё тот же индексный файл, либо для приложения с front controller —index index.php;плюс запасной вариантtry_files $uri $uri/ /index.php?$query_string;.
Не включайте autoindex on; только чтобы убрать 403 — это заменяет ошибку на публичный список всех файлов каталога.
Причина 2: может ли nginx прочитать файлы?
Главный процесс nginx работает от root, но воркеры, которые читают ваши файлы, работают от непривилегированного пользователя: www-data в пакетах Debian и Ubuntu, nginx — в Rocky, Alma, Alpine, в пакетах с nginx.org и в официальном образе Docker. Проверьте, а не угадывайте:
ps -o user=,args= -C nginx # воркеры покажут нужного пользователя
grep -n '^user' /etc/nginx/nginx.confЭтому пользователю нужно право на чтение файла и право на выполнение (проход) на каждый каталог выше него. Одной отсутствующей x в любом месте пути достаточно для 403. namei показывает всю цепочку целиком:
namei -om /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--- alice alice site <- нет x для остальных: 403
# -rw-r--r-- alice alice index.html
# проверка от имени пользователя nginx (nginx вместо www-data на Rocky, Alma, Alpine)
sudo -u www-data cat /var/www/site/index.html > /dev/null && echo readableКлассический случай — сайт внутри /root или домашнего каталога. Новые домашние каталоги в современных Debian и Ubuntu закрыты для остальных пользователей, и nginx не может пройти через них, какими бы открытыми ни были файлы внутри. Чистое решение — отдавать сайт из /var/www или /srv, принадлежащих вашему deploy-пользователю и доступных на чтение всем:
sudo mkdir -p /var/www/site
sudo chown -R $USER:$USER /var/www/site
find /var/www/site -type d -exec chmod 755 {} +
find /var/www/site -type f -exec chmod 644 {} +Если сайт обязательно должен остаться в домашнем каталоге, chmod o+x /home/alice пускает nginx проходить через него без возможности листинга — это работает, но тогда любая локальная учётная запись получает доступ к любому файлу внутри, если его собственные права это позволяют. Файлы, скопированные с другого сервера, часто приходят со старыми идентификаторами владельца или слишком строгим режимом. В Docker файлы, смонтированные через bind-mount, должны быть читаемы пользователем nginx внутри контейнера, а не только вами на хосте.
Причина 3: блокирует ли SELinux nginx на Rocky, Alma или RHEL?
Если права доступа выше в порядке, а лог всё ещё пишет (13: Permission denied), смотрите на SELinux. По умолчанию он в режиме enforcing на Rocky, AlmaLinux и RHEL и пускает nginx читать только файлы с меткой веб-контента, такой как httpd_sys_content_t. Файлы, созданные в домашнем каталоге или перемещённые из него через mv, сохраняют старую метку — и получают 403 при идеальных правах Unix.
getenforce # Enforcing?
ls -Z /srv/site # ищем httpd_sys_content_t
sudo ausearch -m avc -ts recent # отказы, которые залогировал SELinuxsudo dnf install -y policycoreutils-python-utils # даёт semanage
sudo semanage fcontext -a -t httpd_sys_content_t '/srv/site(/.*)?'
sudo restorecon -Rv /srv/site/var/www/html уже несёт нужную метку по умолчанию, так что для файлов, перемещённых туда, достаточно sudo restorecon -Rv /var/www/html. chcon тоже меняет метку, но последующий полный релейбл её сбрасывает; semanage вместе с restorecon переживает его. Каталогам, в которые приложение пишет, нужна метка httpd_sys_rw_content_t. Не «лечите» это setenforce 0 или отключением SELinux целиком — так вы снимаете защиту со всех служб на машине, чтобы исправить метку одного каталога.
Причина 4: совпадает ли правило deny?
access forbidden by rule значит, что запросу подошла директива allow/deny. Найдите их все в загруженной конфигурации:
sudo nginx -T | grep -nE 'deny|allow|satisfy'- Белый список, в который вы не попадаете.
allow 203.0.113.0/24; deny all;— обычный способ защитить админский путь, и на phpMyAdmin 403 от него — это защита, а не ошибка. За CDN или прокси nginx видит адрес прокси, если не настроеныset_real_ip_fromиreal_ip_header. - Блок dotfile-ов, который заодно ловит Let’s Encrypt.
location ~ /\. { deny all; }защищает.gitи.env, но также совпадает с/.well-known/acme-challenge/, и проверка сертификата падает с 403. Исключите именно этот путь (пример ниже). - Базовая авторизация. Неверный пароль с
auth_basicвозвращает 401, а не 403. 403 на пути с паролем идёт от половины allow/deny в блокеsatisfy.
location ^~ /.well-known/acme-challenge/ {
allow all;
}
location ~ /\.(?!well-known) {
deny all;
}Причина 5: действительно ли 403 от nginx?
Если лог ошибок молчит, проверьте, кто на самом деле ответил:
curl -sI https://example.com/path | grep -iE '^(HTTP|server)'- CDN или WAF в заголовке
server— его файрвол заблокировал запрос, не дойдя до вас. Страница блокировки выглядит иначе, чем простая страница nginx, и правка делается в панели этого сервиса. server: nginx, но страница — от вашего приложения — nginx передал запрос дальше, а 403 вернуло приложение: защитный плагин WordPress, проверка CSRF, middleware. Лог доступа покажет 403, лог ошибок — нет; смотрите логи самого приложения.- Тело ответа говорит только
Access denied.— PHP-FPM отказал скрипту, чьё имя не кончается на.php, это его правилоsecurity.limit_extensions. Лог ошибок nginx покажет это какFastCGI sent in stderrсhas been denied (see security.limit_extensions). Правьте location в nginx, чтобы до PHP-FPM доходили только файлы.php, а не расширяйте список.
Что бы вы ни меняли, проверяйте перед перезагрузкой — sudo nginx -t ловит опечатку до того, как она положит сайт, а reload применяет изменение без разрыва соединений. На Alpine используйте rc-service nginx reload.
Частые вопросы
Что значит 403 Forbidden в nginx?
Nginx нашёл запрошенный путь, но отказывается его отдавать. Лог ошибок называет причину в одной строке: directory index of … is forbidden — нет индексного файла, (13: Permission denied) — права файловой системы или SELinux, access forbidden by rule — сработало правило allow/deny. Сначала читайте /var/log/nginx/error.log, потом исправляйте именно эту причину.
Как исправить 403 Forbidden, если нет индексного файла?
Проверьте, что в папке действительно есть index.html или index.php (ls -la), и что директива root в конфигурации указывает на правильную папку — фреймворки часто отдают файлы из подкаталога вроде public/ или dist/. Для PHP-сайта добавьте index.php первым в список index. Не включайте autoindex on только чтобы убрать ошибку — так вы откроете публичный список всех файлов каталога.
Почему SELinux вызывает 403 Forbidden на Rocky или AlmaLinux?
SELinux по умолчанию в режиме enforcing на Rocky, AlmaLinux и RHEL и пускает nginx читать только файлы с меткой веб-контента, такой как httpd_sys_content_t. Файлы, созданные в домашнем каталоге или перемещённые туда через mv, сохраняют старую метку и получают 403 даже при правильных правах Unix. Исправляют это командами sudo semanage fcontext и sudo restorecon, а не отключением SELinux целиком.