Ошибка 403 Forbidden NGINX: как найти причину и исправить
NGINX отвечает 403 Forbidden nginx, когда получил запрос и разобрал его, но отказался отдавать содержимое: это не «файла нет» – тогда был бы код 404, и не «нужен вход» – это 401 или 407.
403 forbidden nginx как исправить? Короткий план: сначала error.log называет причину без гаданий, а дальше – конкретная правка: индекс, права, автоиндекс, SELinux, запрет в конфиге или ошибка в alias.
- Прочитайте
error.log– часто он прямо называет причину. - Проверьте права доступа и бит
xна пути к файлу. - Убедитесь, что есть индексный файл или включен автоиндекс.
- На RHEL-совместимых системах проверьте SELinux.
- Поищите запрет
denyв конфиге. - Проверьте
rootиalias, если путь выглядит верным, а сервер все равно отказывает.
- Что означает 403 Forbidden в NGINX
- С чего начать: error.log
- Где лежат конфиги и какой из них ваш
- Нет индексного файла
- Листинг каталога (autoindex)
- Права доступа и владелец
- Под каким пользователем работает NGINX
- SELinux и AppArmor
- Запрет в конфиге: deny, allow и скрытые файлы
- Ошибки в root и alias
- Проверка и перезапуск
- Чек-лист: 403 за пять минут
Что означает 403 Forbidden в NGINX
«403 Forbidden» – код состояния HTTP: сервер получил запрос, разобрал его и отказался выполнять дальше. NGINX отдает 403 сам, когда запрет стоит в конфигурации, в правах доступа или в правиле deny.

Тот же код 403 может прийти и от приложения за NGINX – PHP-FPM или другого upstream, которому NGINX передал запрос через proxy_pass или fastcgi_pass. Разница не во внешнем виде ошибки, а в источнике: NGINX ее не формировал, а просто передал ответ дальше. Как отличить один случай от другого по логам – в следующем разделе.
С чего начать: error.log
Первый шаг – не правки конфига, а строка в логе. Она называет причину точно, без гаданий:
sudo tail -n 50 /var/log/nginx/error.logПрежде чем что-то менять, проверьте, что сам конфиг синтаксически цел:
sudo nginx -t| Строка в error.log | Причина | Что читать дальше |
| directory index of "..." is forbidden | нет индексного файла, автоиндекс выключен | «Нет индексного файла», «Листинг каталога» |
| open() "..." failed (13: Permission denied) | права доступа, владелец файла – или SELinux на RHEL-совместимых системах | «Права доступа и владелец», «SELinux и AppArmor» |
| access forbidden by rule | запрет deny в конфиге | «Запрет в конфиге: deny, allow и скрытые файлы» |
| рядом с этим запросом в error.log нет ни одной строки из этой таблицы | отказал не NGINX, а приложение за ним | лог самого приложения (PHP-FPM, другой upstream) |
Строка open() "..." failed (13: Permission denied) означает, что ядро Linux отказало в доступе. Обычно дело в правах или владельце файла, но на RHEL-совместимых системах ту же строку пишет и отказ SELinux – ядро возвращает NGINX один и тот же код ошибки в обоих случаях. Первая проверка:
sudo ausearch -m avc -ts recentЕсть записи – причина в SELinux, идите в раздел «SELinux и AppArmor». Но пустой ответ SELinux не исключает: часть отказов подавляют правила dontaudit, и в аудит они не попадают, а если auditd не запущен, отказы уходят не в ausearch, а в journalctl. Надежная проверка – временно перевести SELinux в мягкий режим и повторить запрос:
sudo setenforce 0
# повторите запрос к сайту
sudo setenforce 1Ошибка пропала при выключенном SELinux – дело было в нем: переходите к разделу «SELinux и AppArmor» и настраивайте контекст, а не отключайте защиту насовсем. Ошибка осталась – причина в правах Linux, идите в «Права доступа и владелец».
Если же рядом с этим запросом в error.log NGINX нет ни одной строки из таблицы выше, а браузер все равно получил 403 – отказал не сам NGINX. В access.log код 403 будет, а строки-причины рядом нет: значит, ответ сформировал PHP-FPM или другой upstream. Причину ищите в его собственном логе, например /var/log/php-fpm/www-error.log.
Где лежат конфиги и какой из них ваш
Главный файл – /etc/nginx/nginx.conf, он подключает остальные. В Ubuntu 22.04/24.04 и Debian 12/13 сайты лежат в /etc/nginx/sites-available/, а рабочие ссылки – в /etc/nginx/sites-enabled/. В Rocky Linux и AlmaLinux 9/10 конфиги сайтов – в /etc/nginx/conf.d/*.conf, каталога sites-available там нет.
Раскладка зависит от дистрибутива и способа установки, поэтому нужный файл проще найти по имени домена:
sudo grep -R server_name /etc/nginxКоманда покажет файл и строку, где объявлен ваш блок server. Ubuntu 16.04 и CentOS 7 официально сняты с поддержки – в 2021 и 2024 году соответственно, – и на них проще обновить систему, чем донастраивать NGINX.
Нет индексного файла
Директива index задает, какие файлы NGINX ищет в каталоге и в каком порядке:
index index.html index.htm index.php;Файлы проверяются слева направо. Не найден ни один – и при выключенном автоиндексе NGINX вернет 403 со строкой directory index of "..." is forbidden в логе.
Имена файлов регистрозависимы: index.html и Index.html – разные файлы для NGINX, хотя выглядят одинаково на экране. Свое имя добавляют в конец той же строки:
index index.html index.htm index.php index.py;Листинг каталога (autoindex)
Директивы autoindex и autoindex_exact_size пишутся строчными буквами – регистр здесь важен так же, как у путей. Значение по умолчанию – off: без индексного файла NGINX просто отказывает, а не показывает список файлов каталога.
location / {
autoindex on;
autoindex_exact_size off;
}Директивы можно поставить и на весь сайт, и на один location, например только на /myfiles: тогда листинг откроется в одном разделе, а не по всему сайту.
Автоиндекс показывает список всех файлов каталога любому посетителю. На боевом сайте включать его по умолчанию не стоит – так наружу уходят резервные копии и служебные файлы.
Права доступа и владелец
Посмотреть текущие права каталога сайта:
ls -la /var/www/example.comdrwx------ 3 deploy deploy 4096 Sep 10 09:00 .
drwxr-xr-x 4 root root 4096 Sep 10 09:00 ..
-rw-r--r-- 1 deploy deploy 512 Sep 10 09:00 index.htmlВ этом примере каталог закрыт от всех, кроме владельца – drwx------, права 700 вместо нужных 755. Именно это и дает 403 при верных на вид правах файла внутри. Стандартная схема:
| Объект | Права | Зачем |
| Каталог сайта | 755 | владелец пишет, остальные читают и заходят внутрь |
| Обычный файл (html, css, js) | 644 | владелец пишет, остальные только читают |
| Приватный ключ TLS | 600 | читает только владелец файла |
Отдельная причина 403, которую легко пропустить: NGINX должен иметь бит x на всех каталогах пути до корня сайта, а не только на самом корневом каталоге. Без него рабочий процесс не может зайти внутрь пути, даже если права на конечный файл верные. Проверить всю цепочку одной командой:
namei -l /var/www/example.com/index.htmlf: /var/www/example.com/index.html
drwxr-xr-x root root /
drwxr-xr-x root root var
drwxr-xr-x root root www
drwx------ deploy deploy example.com
-rw-r--r-- deploy deploy index.htmlИщите строку без буквы x в правах каталога – в этом примере разрыв на example.com. Исправить права каталогов и файлов раздельно:
sudo find /var/www/example.com -type d -exec chmod 755 {} \;
sudo find /var/www/example.com -type f -exec chmod 644 {} \;Рабочему процессу нужен доступ на чтение, а не владение файлами. Раздавать chown -R nginx:nginx на весь каталог сайта – плохая практика: владельцем обычно остается пользователь деплоя. При правах 755/644 файлы и так читают все, и группа NGINX ничего не меняет; добавлять его в группу владельца стоит, только если каталоги закрыты от посторонних – 750 для каталогов и 640 для файлов. Тогда группа читает файлы, а остальные – нет:
# Debian, Ubuntu – рабочий процесс от www-data
sudo usermod -aG deploy www-data
# Rocky Linux, AlmaLinux и другие RHEL-совместимые – от nginx
sudo usermod -aG deploy nginxИмя пользователя берется из директивы user в nginx.conf – подробнее в следующем разделе. Команда с чужим именем ответит ошибкой «user 'nginx' does not exist» на Debian и Ubuntu, где такого пользователя нет вовсе.
Группа обновится только у новых рабочих процессов – они читают /etc/group заново при старте. Полный restart для этого не нужен: обычный reload останавливает старые рабочие процессы и поднимает новые, уже с обновленной группой:
sudo systemctl reload nginxПод каким пользователем работает NGINX
Источник правды – директива user в nginx.conf. При сборке из исходников по умолчанию это nobody, но пакеты дистрибутивов ее меняют: в Debian и Ubuntu – www-data, в Rocky Linux, AlmaLinux и других RHEL-совместимых системах – nginx.
Проверить, от чьего имени процесс работает на самом деле, можно командой:
ps -ef | grep nginxПример вывода:
root 1044 1 0 09:12 ? 00:00:00 nginx: master process /usr/sbin/nginx
www-data 1045 1044 0 09:12 ? 00:00:00 nginx: worker process
www-data 1046 1044 0 09:12 ? 00:00:00 nginx: worker processМастер-процесс запущен от root, а рабочие процессы, которые и читают файлы сайта, – от пользователя из директивы user.
SELinux и AppArmor
На Rocky Linux, AlmaLinux и других RHEL-совместимых системах включен SELinux, и он может отказать в доступе даже при верных правах Linux. Текущий режим показывает команда:
getenforceEnforcingКонтекст каталога смотрят так:
ls -Zd /var/www/example.comunconfined_u:object_r:default_t:s0 /var/www/example.comКонтекст default_t в этом выводе неверный: каталоги сайта должны быть в контексте httpd_sys_content_t – его использует и Apache, и NGINX. Команда chcon меняет контекст сразу, но только до ближайшей перемаркировки файловой системы (restorecon или autorelabel): после нее контекст возвращается к прежнему, и 403 – вместе с ним. Постоянный способ – добавить правило в политику и применить его:
sudo semanage fcontext -a -t httpd_sys_content_t "/var/www/example.com(/.*)?"
sudo restorecon -Rv /var/www/example.comПервая команда запоминает контекст для каталога и всего, что появится в нем позже, вторая – применяет его к уже существующим файлам.
Для проксирования на upstream нужен еще один булев переключатель. К 403 он отношения не имеет: без него NGINX не сможет соединиться с upstream и вернет 502, а не 403 – включайте его только при этой ошибке.
sudo setsebool -P httpd_can_network_connect onФлаг -P делает изменение постоянным – без него оно пропадет после перезагрузки сервера.
Не оставляйте setenforce 0 постоянным решением. Это временно отключает всю защиту SELinux на сервере – годится для диагностики, а не для боевой настройки.
В Debian и Ubuntu штатный пакет NGINX не поставляет собственный профиль AppArmor, поэтому эта система редко становится причиной 403. Заподозрили ее – ищите отказы командой:
sudo journalctl -k | grep -i apparmorЗапись со словом DENIED подтвердит, что дело именно в AppArmor, а не в обычных правах.
Запрет в конфиге: deny, allow и скрытые файлы
Еще одна причина 403 – прямой запрет в конфигурации:
location / {
deny 192.168.1.100;
allow all;
}Строка в логе для этого случая – та же access forbidden by rule, что уже была в таблице выше. Так же закрывают доступ к скрытым файлам и служебным путям – правилом по регулярному выражению:
location ~ /\. {
deny all;
}Базовая авторизация работает иначе: без верных учетных данных она отдает 401, а не 403, и оставляет в логе другую отметку. Увидели 403 рядом с auth_basic – причина не в самой авторизации, а в соседнем правиле, которое сработало раньше, например в deny.
Найти правила deny, allow и auth_basic в своих конфигах помогает поиск по ключевым словам:
sudo grep -R "deny\|allow\|auth_basic" /etc/nginxОшибки в root и alias
Директива alias заменяет путь location целиком, а не добавляет к нему, поэтому в ней важен каждый слеш.
Первая ошибка – location заканчивается слешем, а alias – нет:
location /files/ {
alias /data/uploads;
}Запрос /files/report.pdf склеивается без разделителя – получается путь /data/uploadsreport.pdf. Такого файла нет, и NGINX отдает 404, а не 403: искать здесь отказ доступа бессмысленно, путь просто не существует.
Обратная ошибка опаснее: location без завершающего слеша при alias со слешем.
location /files {
alias /data/uploads/;
}NGINX сопоставляет такой location по началу строки, а не по целому сегменту пути, и запрос вроде /files../etc/passwd может увести alias за пределы каталога uploads. Это не типичная причина именно вашего 403: обычный запрос вроде /files/report.pdf такой location обслужит штатно, а утечка проявляется только на специально составленном пути и обычно дает не 403, а содержимое чужого файла или 404, если пути не существует. Опасность здесь другая: рядом с настоящей проблемой сайт может молча отдавать файлы за пределами каталога. Правильно – слеш на обеих сторонах:
location /files/ {
alias /data/uploads/;
}Директива root работает иначе – она добавляет путь location к своему значению, поэтому такой ошибки со слешем у нее нет. Две частые причины 403 остаются за самим значением пути:
rootуказывает на каталог уровнем выше настоящего корня сайта – сравните значениеrootв конфиге с реальным расположением файлов:grep -R root /etc/nginx/sites-enabledпокажет путь из конфига, аls -laпо нему – лежат ли там ожидаемые файлы сайта.- symlink в корне сайта ведет на раздел, к которому у процесса NGINX нет доступа – проверьте цель ссылки и права на нее той же командой, что и в разделе «Права доступа и владелец»:
namei -l /var/www/example.com/index.htmlпокажет разрыв на самой ссылке или на каталоге, куда она ведет.
Проверка и перезапуск
Перед любым перезапуском проверяют синтаксис:
sudo nginx -tТолько после успешной проверки перезагружают конфигурацию:
sudo systemctl reload nginxИли тот же результат другой командой:
sudo nginx -s reloadreload заставляет NGINX перечитать конфиг и поднять новые рабочие процессы, не разрывая уже открытые соединения. restart останавливает процесс целиком и запускает заново – на боевом сайте это короткая, но заметная пауза.
Чек-лист: 403 за пять минут
- Прочитайте
error.logи найдите причину по таблице выше. - Проверьте синтаксис конфига (
sudo nginx -t) и найдите свой файл по домену:grep -R server_name /etc/nginx. - Проверьте индексный файл и его имя в точности, включая регистр.
- Проверьте права и бит
xпо всему пути:namei -l /путь/к/файлу, при ошибке –chmod 755на каталоги иchmod 644на файлы. - Узнайте пользователя NGINX (
ps -ef | grep nginx) и на RHEL-совместимых системах проверьте SELinux:getenforce,ls -Zd, при ошибке контекста –semanage fcontextиrestorecon. - Поищите запрет в конфиге:
grep -R "deny\|allow\|auth_basic" /etc/nginx. - Сверьте
rootиaliasс реальным расположением файлов и целью symlink:namei -l. - Перезагрузите конфигурацию:
sudo systemctl reload nginx.
Совет редакции: начинайте с error.log, а не с правки конфига наугад – строка в логе экономит несколько кругов проверок вслепую.
Комментарии
Выполняю: sudo chown -R nginx:nginx *
И получаю в ответ: chown: invalid user: ‘nginx:nginx’
nginx установлен последний для ubuntu, работает процесс, но получаю 403 Forbidden
Проверил /etc/passwd и /etc/group - пользователя и группы nginx не существует.
Как это так, подскажите?
у тебя ответ: chown: invalid user: ‘nginx:nginx’
возможно ты используешь root, и поэтому надо так:
sudo chown -R root:root *