> Полный список статей есть в [llms.txt](https://docs.f3cloud.com/llms.txt). Markdown-версия есть у каждой страницы, достаточно добавить .md к её адресу. Эта страница в Markdown: [https://docs.f3cloud.com/ru/web/site-errors-502-504-403-500.md](https://docs.f3cloud.com/ru/web/site-errors-502-504-403-500.md).

# Ошибки 502, 504, 403 и 500 в Nginx – что они значат и как исправить

Что значат ошибки 502 Bad Gateway, 504 Gateway Time-out, 403 Forbidden и 500 Internal Server Error на сервере с Nginx и как их исправить. Цепочка от браузера до приложения, диагностика через curl, журнал error.log и systemctl, разбор частых причин с настоящими строками из журнала, настройка таймаутов и прав доступа. Проверено на Debian 12 с Nginx 1.22.1 и PHP 8.2.

Вы открываете свой сайт, а вместо страницы видите белый экран с надписью **502 Bad Gateway**, **504 Gateway Time-out**, **403 Forbidden** или **500 Internal Server Error**. Номер ошибки выглядит пугающе, но на самом деле он довольно точно подсказывает, на каком участке пути от браузера до сайта что-то сломалось, а настоящая причина записана в журнале Nginx одной строкой. В этой статье мы разберём, как устроен этот путь, научимся находить причину за пару минут и по очереди исправим каждую из четырёх ошибок на примерах, которые встречаются чаще всего. Все примеры мы воспроизвели на тестовом сервере с Debian 12, версия Nginx на момент проверки **1.22.1**, PHP **8.2.34**.

***

## Как устроен путь от браузера до сайта

Когда посетитель открывает страницу, его браузер отправляет запрос на веб-сервер, то есть в Nginx. Если нужна обычная картинка или готовая HTML-страница, Nginx просто берёт файл с диска и отдаёт его. Но большинство сайтов состоит не только из файлов. Страницу нужно собрать, например обратившись к базе данных, и этим занимается отдельная программа, которую называют **бэкендом**. Это может быть PHP-FPM, выполняющий скрипты WordPress, или приложение на Python либо Node.js, которое слушает свой порт. Nginx в такой связке работает привратником. Он принимает запрос, передаёт его бэкенду, ждёт ответ и пересылает его браузеру. Поэтому две из четырёх ошибок в названии содержат слово «шлюз» (Gateway), ведь в этот момент Nginx стоит шлюзом между посетителем и приложением.

Первая цифра кода показывает, на чьей стороне неудача. Код **403** относится к семейству 4xx, то есть к запросам, которые нельзя выполнить в том виде, как они пришли. Nginx прекрасно понял, чего хочет браузер, но отказался отдавать ответ, потому что показывать нечего или у него нет прав прочитать нужные файлы. Коды семейства 5xx означают сбой на стороне сервера. Код **500** говорит, что при обработке запроса что-то сломалось, чаще всего в самом скрипте или приложении. Код **502** означает, что Nginx не получил от бэкенда внятный ответ, например потому, что бэкенд не запущен. Код **504** означает, что бэкенд на связи, но не успел ответить за отведённое время. Поэтому при любой из этих ошибок нужно сначала понять, на каком звене цепочки произошёл обрыв.

Сам код называет звено, но не причину, и причину приходится искать в журнале. Рассмотрим, например, ситуацию, в которой остановился PHP-FPM. Браузер по-прежнему обращается к Nginx, тот пытается передать запрос в сокет PHP-FPM, но подключиться не может, и поэтому сам рисует короткую страницу 502 с номером своей версии внизу, а в журнал записывает, к чему именно он не смог подключиться. Если же PHP-FPM работает, но скрипт упал с фатальной ошибкой, Nginx получает от него ответ с кодом 500 и пустым телом и честно передаёт его дальше, а надпись об ошибке рисует уже сам браузер. Мы видели обе картины на тестовом сервере, и по внешнему виду страницы можно догадаться, кто её выдал, Nginx или приложение. Тем не менее точный ответ лежит в журнале ошибок, к которому мы и перейдём.

***

## Что понадобится

* **Сервер** с Debian 12, на котором уже работает Nginx, например тариф **DE-RZ9-1**. Примеры из статьи очень лёгкие, а подходящий тариф для вашего сайта поможет выбрать статья про [VPS](https://docs.f3cloud.com/ru/vps/cloud-server-types/vps-hosting.md). Если сервера ещё нет, закажите его по [инструкции](https://docs.f3cloud.com/ru/client-area/order-vps.md).
* **Подключение по SSH**, например через [Termius](https://docs.f3cloud.com/ru/vps/termius-connection.md). Команды выполняются от имени `root`.
* **Nginx и PHP-FPM**. Как их установить, мы описали в статье про [установку Nginx](https://docs.f3cloud.com/ru/web/nginx-installation.md). PHP-FPM нужен только для примеров с PHP.
* **Сайт или приложение**, на котором вы видите ошибку.

> **Адреса и имена в примерах.**
>
> На тестовом сервере каждый пример работал на своём порту, поэтому в командах и на скриншотах терминала вместо домена указан адрес вида `127.0.0.1:8091`. На ваш сайт ссылайтесь по домену, например `https://example.com`. Папки `errors-demo-…` и служба `errors-app` тоже придуманы для статьи, у вас будут свои имена. Скриншоты браузера сняты с тех же тестовых сайтов, домен `example.com` в адресной строке условный.

***

## Шаг 1. Узнайте код ответа

Начните с команды `curl`. Она обращается к сайту так же, как браузер, но показывает только то, что действительно ответил сервер. Параметр `-I` просит прислать только заголовки ответа, а код стоит в самой первой строке.

```bash
# Показываем заголовки ответа, в первой строке будет код
curl -I https://example.com
```

Если нужна только строка с кодом, добавьте параметр `-s` и передайте вывод команде `head`.

```bash
# Только первая строка ответа
curl -sI https://example.com | head -1
```

Строка вида `HTTP/1.1 502 Bad Gateway` подтверждает, что ошибку выдал сервер, а не ваша сеть или браузер. Заодно в заголовке `Server` видна версия Nginx.

***

## Шаг 2. Прочитайте журнал ошибок Nginx

Каждый отказ Nginx записывает в файл `/var/log/nginx/error.log`. Откройте журнал в режиме слежения, а затем в браузере обновите страницу с ошибкой. Новая строка появится в тот же момент.

```bash
# Следим за журналом, выход по Ctrl+C
tail -f /var/log/nginx/error.log
```

> **Старые строки не мешают.**
>
> Команда `tail -f` сначала показывает последние десять строк журнала и только потом ждёт новых. Чтобы старые записи не отвлекали, добавьте параметр `-n 0`, как на скриншоте ниже.

<figure><img src="https://docs.f3cloud.com/img/errors-diagnosis.png" alt="Команда curl -I показывает 502 Bad Gateway, а tail -f выводит строку connect() failed (111: Connection refused) из error.log"><figcaption><p>Код ответа и строка из журнала, которая объясняет причину</p></figcaption></figure>

Разобрать такую строку несложно. Возьмём ту, что мы получили, когда приложение за Nginx было остановлено.

```text
2026/10/21 14:53:33 [error] 124894#124894: *3 connect() failed (111: Connection refused) while connecting to upstream, client: 127.0.0.1, server: example.com, request: "GET / HTTP/1.1", upstream: "http://127.0.0.1:8098/", host: "example.com"
```

* **Время и уровень** в начале строки. Слово в квадратных скобках показывает серьёзность записи, у нас встречались `[error]` и `[crit]`.
* **Суть проблемы** сразу после номера процесса. Здесь это `connect() failed (111: Connection refused)`, то есть Nginx не смог подключиться, потому что порт закрыт.
* **client** показывает, кто обратился, **server** и **host** показывают, к какому сайту, а **request** содержит сам запрос.
* **upstream** показывает, куда именно Nginx пытался передать запрос. Это адрес бэкенда, и он подсказывает, какую программу проверять дальше.

Журнал общий для всех сайтов на сервере, поэтому при большом трафике нужную запись удобно искать по адресу страницы.

```bash
# Последние записи, в которых встречается нужный адрес
grep '/slow' /var/log/nginx/error.log | tail -n 5
```

Подробнее о том, как читать логи и где они лежат, рассказано в статье про [логи в Linux](https://docs.f3cloud.com/ru/linux/linux-logs.md).

***

## Шаг 3. Проверьте программу за Nginx

Если в строке журнала есть слово `upstream`, дальше нужно выяснить, что происходит с бэкендом. Для приложения, которое работает как служба systemd, проверьте её состояние и последние сообщения. Вместо `myapp` подставьте имя вашей службы, а для PHP это будет `php8.2-fpm`.

```bash
# Состояние службы
systemctl status myapp --no-pager

# Последние 50 сообщений службы
journalctl -u myapp -n 50 --no-pager

# Кто слушает нужный порт, вместо 8098 подставьте свой
ss -tlnp | grep 8098
```

В строке **Active** должно быть `active (running)`. Значения `failed` и `inactive (dead)` означают, что программа остановилась, а причину обычно рассказывает журнал службы. Последняя команда показывает программы, которые слушают порты. Пустой ответ значит, что на этом порту никто не слушает и Nginx некому передавать запросы. Как управлять службами и читать их логи, подробно описано в статье про [службы systemd](https://docs.f3cloud.com/ru/linux/systemd-services.md).

<figure><img src="https://docs.f3cloud.com/img/errors-502-app-failed.png" alt="systemctl status показывает failed, journalctl показывает FileNotFoundError, команда ss ничего не находит на порту 8098"><figcaption><p>Служба в состоянии failed, в журнале причина, порт 8098 никто не слушает</p></figcaption></figure>

На скриншоте служба `errors-app` упала сразу после запуска. В журнале видна причина, `FileNotFoundError`, то есть приложению не хватило файла настроек. Зная номер ошибки и строку из журнала, можно переходить к разбору конкретной причины.

***

## Ошибка 502 Bad Gateway

Страница 502 означает, что Nginx принял запрос, но не смог получить ответ от бэкенда. Она выглядит как короткая белая страница с номером версии Nginx внизу.

<figure><img src="https://docs.f3cloud.com/img/errors-502-browser.png" alt="Страница 502 Bad Gateway с надписью nginx/1.22.1"><figcaption><p>Так выглядит ошибка 502 в браузере</p></figcaption></figure>

Мы воспроизвели три самые частые причины, и у каждой в журнале своя строка.

### Приложение не запущено

Это самая простая и самая частая причина. В настройках Nginx сайт передаёт запросы приложению.

```nginx
location / {
    proxy_pass http://127.0.0.1:8098;
}
```

Если приложение остановилось, упало или после перезагрузки сервера так и не запустилось, на его порту никого нет. Журнал при этом содержит уже знакомую строку.

```text
2026/10/21 14:53:33 [error] 124894#124894: *3 connect() failed (111: Connection refused) while connecting to upstream, client: 127.0.0.1, server: example.com, request: "GET / HTTP/1.1", upstream: "http://127.0.0.1:8098/", host: "example.com"
```

Код `111: Connection refused` буквально означает, что соединение отвергнуто, потому что порт закрыт. В нашем примере причину показал журнал службы, который вы видели на скриншоте выше. Приложение не стартовало, потому что мы переименовали его файл настроек. Верните файл на место и запустите службу.

```bash
# Возвращаем файл настроек на место
mv /opt/errors-demo/config.json.old /opt/errors-demo/config.json

# Запускаем службу и проверяем ответ сайта
systemctl start errors-app
curl -I http://127.0.0.1:8091
```

<figure><img src="https://docs.f3cloud.com/img/errors-502-app-fixed.png" alt="После запуска службы curl -I возвращает 200 OK, а curl показывает ответ приложения"><figcaption><p>Служба запущена, сайт снова отвечает кодом 200</p></figcaption></figure>

Чтобы такая ошибка не повторялась после каждого сбоя, поручите systemd самому поднимать приложение. Для этого в файл службы добавьте две строки в раздел `[Service]`.

```ini
[Service]
# Перезапускаем приложение, если оно упало
Restart=on-failure
# Ждём три секунды перед перезапуском
RestartSec=3
```

Затем перечитайте настройки и включите автозапуск при загрузке сервера.

```bash
# Применяем изменения в файле службы
systemctl daemon-reload

# Включаем запуск вместе с сервером
systemctl enable errors-app
```

Проверка получилась наглядной. Мы принудительно завершили процесс командой `systemctl kill -s KILL errors-app`. Сразу после этого сайт отвечал кодом 502, а через три секунды systemd запустил приложение заново и сайт снова открылся. Заметьте, что до включения автозапуска в строке **Loaded** на скриншоте стояло слово `disabled`. Оно означает, что после перезагрузки сервера служба сама не стартует.

### PHP-FPM, сокет не найден

Для сайтов на PHP Nginx передаёт запросы не на порт, а в сокет, то есть специальный файл, через который он общается с PHP-FPM. Путь к нему указан в директиве `fastcgi_pass`.

```nginx
location ~ \.php$ {
    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/run/php/php8.1-fpm.sock;
}
```

Если такого файла нет, Nginx отвечает 502, а журнал сообщает об этом открытым текстом.

```text
2026/10/21 14:53:33 [crit] 124894#124894: *5 connect() to unix:/run/php/php8.1-fpm.sock failed (2: No such file or directory) while connecting to upstream, client: 127.0.0.1, server: example.com, request: "GET / HTTP/1.1", upstream: "fastcgi://unix:/run/php/php8.1-fpm.sock:", host: "example.com"
```

Код `2: No such file or directory` означает, что файла сокета не существует. Чаще всего причин две. Либо в настройках указано имя от другой версии PHP, например в инструкции для PHP 8.1, а у вас стоит 8.2, либо служба PHP-FPM остановлена, и её сокет исчез вместе с ней. Посмотрите, какие сокеты есть на сервере.

```bash
# Список сокетов PHP-FPM
ls /run/php/
```

<figure><img src="https://docs.f3cloud.com/img/errors-502-php-socket.png" alt="curl показывает 502, в error.log стоит php8.1-fpm.sock failed (2: No such file or directory), а в папке /run/php лежит php8.2-fpm.sock"><figcaption><p>В настройках указан php8.1-fpm.sock, а на сервере есть только php8.2-fpm.sock</p></figcaption></figure>

В нашем примере в папке лежали `php8.2-fpm.sock` и `php-fpm.sock`. Исправьте имя в `fastcgi_pass`, проверьте настройки и примените их.

```bash
# Меняем версию в имени сокета
sed -i 's/php8.1-fpm.sock/php8.2-fpm.sock/' /etc/nginx/sites-available/example.com

# Проверяем настройки и применяем их
nginx -t && systemctl reload nginx
```

> **Файл php-fpm.sock.**
>
> В Debian рядом с сокетом конкретной версии лежит ещё `php-fpm.sock`. Это ссылка на сокет установленной версии PHP, и на нашем сервере она вела на `php8.2-fpm.sock`. Если указать в `fastcgi_pass` именно её, имя в настройках не придётся менять при смене версии PHP. Мы проверили, что сайт с таким путём отвечает нормально.

Если имя верное, а сокета всё равно нет, значит, остановился сам PHP-FPM. Мы остановили его командой `systemctl stop php8.2-fpm` и получили ту же ошибку 502 со строкой `connect() to unix:/run/php/php8.2-fpm.sock failed (2: No such file or directory)`. Команда `systemctl status php8.2-fpm` показала `inactive (dead)`, а после запуска службы сайт снова заработал.

<figure><img src="https://docs.f3cloud.com/img/errors-502-php-stopped.png" alt="После остановки php8.2-fpm сайт отвечает 502, в журнале нет файла сокета, systemctl status показывает inactive (dead), после start сайт работает"><figcaption><p>PHP-FPM остановлен, сокета нет, после запуска службы сайт снова открывается</p></figcaption></figure>

Если служба не запускается, причину покажет `journalctl -u php8.2-fpm -n 50 --no-pager`. А после правки настроек PHP-FPM проверяйте их командой `php-fpm8.2 -t`, как мы покажем дальше.

### PHP-FPM, нет доступа к сокету

Бывает и так, что сокет на месте, но Nginx не может им пользоваться. В журнале тогда другой код.

```text
2026/10/21 14:57:38 [crit] 133790#133790: *56 connect() to unix:/run/php/errors-fpm.sock failed (13: Permission denied) while connecting to upstream, client: 127.0.0.1, server: example.com, request: "HEAD / HTTP/1.1", upstream: "fastcgi://unix:/run/php/errors-fpm.sock:", host: "127.0.0.1:8099"
```

<figure><img src="https://docs.f3cloud.com/img/errors-502-sock-denied.png" alt="curl возвращает 502, в журнале connect() to unix:/run/php/errors-fpm.sock failed (13: Permission denied), ls показывает владельца root у файла сокета"><figcaption><p>Сокет на месте, но принадлежит root, и Nginx не может к нему подключиться</p></figcaption></figure>

Код `13: Permission denied` означает, что у Nginx нет прав на этот файл. Nginx работает от имени пользователя `www-data`, а доступ к сокету определяют параметры `listen.owner`, `listen.group` и `listen.mode` в настройках пула PHP-FPM. По документации PHP, если не указать владельца, сокет получает пользователя, от которого запущена служба, а права `0660`. В нашем примере мы создали отдельный пул `errors` в файле `/etc/php/8.2/fpm/pool.d/errors.conf` и не указали владельца.

```ini
[errors]
user = nobody
group = nogroup
listen = /run/php/errors-fpm.sock
pm = ondemand
pm.max_children = 2
pm.process_idle_timeout = 10s
```

Служба запускается от имени `root`, поэтому сокет получил владельца `root` и права `srw-rw----`, а `www-data` в эту группу не входит. Добавьте в пул две строки с владельцем.

```bash
# Переходим в папку с настройками пулов
cd /etc/php/8.2/fpm/pool.d

# Разрешаем пользователю Nginx работать с сокетом
printf 'listen.owner = www-data\nlisten.group = www-data\n' >> errors.conf

# Проверяем настройки и перезапускаем PHP-FPM
php-fpm8.2 -t && systemctl restart php8.2-fpm

# Смотрим владельца сокета и проверяем сайт
ls -l /run/php/errors-fpm.sock
curl http://127.0.0.1:8099/
```

<figure><img src="https://docs.f3cloud.com/img/errors-502-sock-fixed.png" alt="После добавления listen.owner и listen.group сокет принадлежит www-data, curl возвращает ответ PHP"><figcaption><p>Сокет принадлежит www-data, сайт отвечает</p></figcaption></figure>

> **Нужен именно restart.**
>
> Когда мы после правки сделали `systemctl reload php8.2-fpm`, владелец сокета остался прежним, `root`, и ошибка 502 не исчезла. Сокет сменил владельца только после `systemctl restart php8.2-fpm`.

> **Всегда проверяйте настройки перед перезагрузкой.**
>
> Привыкайте запускать `nginx -t` перед каждой командой `reload`. Если в настройках ошибка, проверка её покажет, а работающий Nginx продолжит отвечать по старой конфигурации. Для PHP-FPM есть такая же проверка, команда `php-fpm8.2 -t`.

***

## Ошибка 504 Gateway Time-out

Страница 504 говорит о том, что бэкенд жив и принял запрос, но слишком долго готовит ответ. Nginx ждёт его ограниченное время и, не дождавшись, сам прерывает ожидание. Время ожидания задаёт директива `proxy_read_timeout`, по умолчанию это 60 секунд, а для PHP-FPM такую же роль играет `fastcgi_read_timeout`. Важная деталь из документации Nginx в том, что это время отсчитывается между двумя последовательными чтениями, а не на весь ответ целиком. Если бэкенд присылает очередную порцию данных чаще, чем раз в 60 секунд, соединение не оборвётся.

Чтобы увидеть это своими глазами, мы добавили в тестовое приложение адрес `/slow`, который отвечает только через 75 секунд, как тяжёлый отчёт или долгий запрос к базе. Страница открывалась ровно минуту, а затем появилась ошибка.

<figure><img src="https://docs.f3cloud.com/img/errors-504-browser.png" alt="Страница 504 Gateway Time-out с надписью nginx/1.22.1"><figcaption><p>Ошибка 504 появляется после минуты ожидания</p></figcaption></figure>

Команда `curl` с параметром `-w` позволяет замерить время и увидеть код одним взглядом.

```bash
# Показываем код ответа и время ожидания
curl -s -o /dev/null -w '%{http_code} через %{time_total} с\n' http://127.0.0.1:8093/slow
```

В журнале при этом появилась такая запись.

```text
2026/10/21 15:04:14 [error] 144934#144934: *81 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 127.0.0.1, server: example.com, request: "GET /slow HTTP/1.1", upstream: "http://127.0.0.1:8098/slow", host: "127.0.0.1:8093"
```

<figure><img src="https://docs.f3cloud.com/img/errors-504-log.png" alt="curl возвращает 504 через 60 секунд, в error.log стоит upstream timed out (110: Connection timed out) while reading response header from upstream"><figcaption><p>Код 504 через 60,06 секунды и строка upstream timed out в журнале</p></figcaption></figure>

Слова `while reading response header` означают, что Nginx успешно подключился, отправил запрос и ждал начало ответа. Замер показал 60,06 секунды, то есть Nginx оборвал ожидание ровно по умолчанию.

Исправлять 504 лучше с причины. Выясните, что именно работает долго, например тяжёлый запрос к базе, обращение к чужому сервису или большой отчёт, и ускорьте его. Если задача по своей природе долгая, например выгрузка или импорт, увеличьте время ожидания для нужного адреса. Добавьте в блок `location` директиву `proxy_read_timeout`.

```nginx
location / {
    proxy_pass http://127.0.0.1:8098;
    # Ждём ответ от приложения до двух минут
    proxy_read_timeout 120s;
}
```

Проверьте настройки и примените их, а затем повторите запрос.

```bash
# Проверяем настройки и применяем их
nginx -t && systemctl reload nginx

# Повторяем запрос
curl -s -o /dev/null -w '%{http_code} через %{time_total} с\n' http://127.0.0.1:8093/slow
```

<figure><img src="https://docs.f3cloud.com/img/errors-504-fixed.png" alt="После proxy_read_timeout 120s тот же запрос возвращает 200 через 75 секунд"><figcaption><p>Тот же запрос теперь возвращает 200 через 75 секунд</p></figcaption></figure>

Для сайтов на PHP-FPM делается то же самое, только директива называется `fastcgi_read_timeout` и стоит в блоке `location ~ \.php$` рядом с `fastcgi_pass`. Мы проверили это на скрипте, который отвечает через 70 секунд. С настройками по умолчанию он давал 504 ровно через 60 секунд, а со строкой `fastcgi_read_timeout 120s;` ответил кодом 200 через 70 секунд. Строка в журнале при этом выглядела так.

```text
2026/10/21 15:07:46 [error] 153814#153814: *89 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 127.0.0.1, server: example.com, request: "GET /slow.php HTTP/1.1", upstream: "fastcgi://unix:/run/php/php8.2-fpm.sock", host: "127.0.0.1:8096"
```

> **Таймаут Nginx не отменяет лимит PHP.**
>
> У самого PHP есть собственный предел времени работы скрипта, параметр `max_execution_time`. В файле `/etc/php/8.2/fpm/php.ini` на нашем сервере он равен 30 секундам. Мы запустили скрипт, который непрерывно занимал процессор, и PHP остановил его ровно на 30 секундах. В ответ пришла не 504, а ошибка 500, а в журнале появилась строка `PHP Fatal error: Maximum execution time of 30 seconds exceeded in /var/www/errors-demo-php/busy.php on line 3`. Скрипт, который просто ждал вызовом `sleep(70)`, под это ограничение не попал, потому что по документации PHP время ожидания системных вызовов на Linux не учитывается. Если ваш скрипт упирается именно в лимит PHP, поднимайте `max_execution_time`, а не только таймаут Nginx.

***

## Ошибка 403 Forbidden

Страница 403 означает, что Nginx запрос понял, но отказался на него отвечать. На практике причин две. Либо в папке нечего показывать, либо у Nginx нет прав прочитать файл. В журнале эти случаи легко различить, поэтому всегда начинайте с него.

<figure><img src="https://docs.f3cloud.com/img/errors-403-browser.png" alt="Страница 403 Forbidden с надписью nginx/1.22.1"><figcaption><p>Так выглядит ошибка 403 в браузере</p></figcaption></figure>

### Нет индексного файла

Когда посетитель открывает адрес папки, например `https://example.com/`, Nginx ищет в ней главную страницу. Имя файла задаёт директива `index`, по умолчанию это `index.html`. Если такого файла нет, Nginx мог бы показать список файлов папки, но по умолчанию это выключено (`autoindex off`), и поэтому он отвечает 403.

```text
2026/10/21 14:53:33 [error] 124894#124894: *7 directory index of "/var/www/errors-demo-403a/" is forbidden, client: 127.0.0.1, server: example.com, request: "GET / HTTP/1.1", host: "example.com"
```

Фраза `directory index of … is forbidden` как раз и означает, что в папке нет индексного файла, а список файлов запрещён. Посмотрите, что лежит в папке сайта.

```bash
# Смотрим содержимое папки сайта
ls -la /var/www/errors-demo-403a
```

<figure><img src="https://docs.f3cloud.com/img/errors-403-noindex.png" alt="curl возвращает 403, в журнале directory index of /var/www/errors-demo-403a/ is forbidden, а ls показывает пустую папку"><figcaption><p>В папке сайта нет главной страницы, поэтому Nginx отвечает 403</p></figcaption></figure>

Папка оказалась пустой. Чаще всего так случается, когда сайт загрузили не в ту папку или забыли загрузить сам файл главной страницы. Проверьте, что директива `root` указывает на папку, где реально лежит сайт, и что в ней есть файл с именем из директивы `index`. Если для PHP-сайта это `index.php`, убедитесь, что оно указано в `index index.html index.php;`. После загрузки файла ответ меняется сразу, перезагружать Nginx не нужно.

```bash
# Создаём главную страницу
echo '<h1>Сайт работает</h1>' > /var/www/errors-demo-403a/index.html

# Проверяем ответ
curl -sI http://127.0.0.1:8094/ | head -1
```

После этого сервер ответил `HTTP/1.1 200 OK`.

Если вам нужен именно список файлов, например для папки с архивами, включите его директивой `autoindex on;` в нужном блоке `location`. Мы проверили, что Nginx в этом случае отдаёт страницу `Index of /` со списком файлов папки.

> **Список файлов виден всем.**
>
> При `autoindex on` любой посетитель увидит имена всех файлов в папке и сможет скачать каждый из них. Включайте его только для папок, которые вы готовы показать целиком.

### Нет прав на файл

Во втором случае файл на месте, но Nginx не может его прочитать. Журнал сообщает об этом так.

```text
2026/10/21 14:53:33 [error] 124894#124894: *8 open() "/var/www/errors-demo-403b/index.html" failed (13: Permission denied), client: 127.0.0.1, server: example.com, request: "GET / HTTP/1.1", host: "example.com"
```

Код `13: Permission denied` означает отказ в доступе на уровне Linux. Чтобы понять, чьи права важны, узнайте, от имени кого работает Nginx, а затем посмотрите права на каждый элемент пути командой `namei`.

```bash
# От имени какого пользователя работает Nginx
grep ^user /etc/nginx/nginx.conf

# Права на каждую папку и файл на пути
namei -l /var/www/errors-demo-403b/index.html
```

<figure><img src="https://docs.f3cloud.com/img/errors-403-perm.png" alt="В журнале open() index.html failed (13: Permission denied), в nginx.conf user www-data, namei показывает права -rw------- root root у файла index.html"><figcaption><p>Nginx работает от www-data, а файл закрыт для всех, кроме root</p></figcaption></figure>

Nginx работает от имени `www-data`, а файл имеет права `-rw-------` и принадлежит `root`, то есть читать его может только владелец. Для `www-data` это посторонний пользователь, и доступа у него нет. Такую картину мы получили, создав файл от имени `root` с правами `600`. Для сайта подходит простое правило. Папкам ставят `755`, файлам `644`, а владельцем делают пользователя веб-сервера. Как устроены права и что означают эти цифры, рассказано в статье про [права доступа в Linux](https://docs.f3cloud.com/ru/linux/linux-permissions.md).

```bash
# Передаём сайт пользователю веб-сервера
chown -R www-data:www-data /var/www/errors-demo-403b

# Папкам 755, файлам 644
find /var/www/errors-demo-403b -type d -exec chmod 755 {} +
find /var/www/errors-demo-403b -type f -exec chmod 644 {} +
```

<figure><img src="https://docs.f3cloud.com/img/errors-403-perm-fixed.png" alt="После chown и chmod файл index.html имеет права -rw-r--r-- и владельца www-data, curl возвращает 200 OK"><figcaption><p>Файл доступен для чтения, сайт отвечает кодом 200</p></figcaption></figure>

Для чтения файла Nginx достаточно права `r` для «остальных». Мы проверили, что при владельце `root` и правах `644` сайт тоже открывается, а смена владельца нужна, чтобы сайт мог сам сохранять загружаемые файлы.

> **Не лечите 403 правами 777.**
>
> Права `777` разрешают всем пользователям сервера читать и изменять файл, и ошибка действительно пропадает. Но вместе с ней пропадает и защита сайта, поэтому такой совет из интернета лучше не применять. Задайте владельцем пользователя `www-data` и выставьте `755` и `644`.

### Нет прав на папку на пути к файлу

Иногда сам файл доступен всем, а ошибка всё равно остаётся. Чтобы добраться до файла, Nginx должен пройти через каждую папку на пути, и для этого у `www-data` на каждой из них нужно право `x`. Достаточно, чтобы одна папка оказалась закрыта. Журнал в этом случае выглядит немного иначе, в нём нет слова `open()`.

```text
2026/10/21 15:08:10 [error] 156517#156517: *109 "/var/www/errors-demo-403c/public/index.html" is forbidden (13: Permission denied), client: 127.0.0.1, server: example.com, request: "GET / HTTP/1.1", host: "127.0.0.1:8095"
```

В нашем примере файл `index.html` имеет права `644`, но папка выше него закрыта. Найти её поможет всё та же команда `namei`.

```bash
# Ищем закрытую папку на пути к файлу
namei -l /var/www/errors-demo-403c/public/index.html
```

<figure><img src="https://docs.f3cloud.com/img/errors-403-dir.png" alt="namei показывает права drwx------ у папки errors-demo-403c, после chmod 755 сайт отвечает 200 OK"><figcaption><p>Папка errors-demo-403c закрыта для всех, кроме root, после chmod 755 сайт открывается</p></figcaption></figure>

В выводе у папки `errors-demo-403c` стоят права `drwx------`, остальные папки открыты. Откройте её для чтения и входа.

```bash
# Разрешаем входить в папку всем пользователям
chmod 755 /var/www/errors-demo-403c
```

Такая картина типична для сайтов, которые лежат в закрытой папке, например в домашней. На нашем сервере у папки `/root` права `700`, поэтому выкладывать сайт в неё не стоит. Для сайтов используйте `/var/www`.

***

## Ошибка 500 Internal Server Error

Код 500 называют самым неопределённым, потому что он означает просто «что-то пошло не так при обработке запроса». Поэтому здесь особенно важно заглянуть в журнал. Мы воспроизвели две причины, которые встречаются чаще других. Первая относится к PHP, вторая к самому Nginx.

### Фатальная ошибка в PHP

Если PHP-скрипт упал, PHP-FPM отвечает кодом 500. По умолчанию PHP не показывает посетителю текст ошибки, и в нашем случае параметр `display_errors` в `/etc/php/8.2/fpm/php.ini` был выключен, а `log_errors` включён. Тело ответа оказалось пустым, поэтому надпись нарисовал сам браузер, а не Nginx.

<figure><img src="https://docs.f3cloud.com/img/errors-500-php-browser.png" alt="Страница браузера «Страница недоступна» с надписью HTTP ERROR 500"><figcaption><p>При пустом ответе браузер показывает собственную страницу с кодом HTTP ERROR 500</p></figcaption></figure>

Зато всё, что хотел сказать PHP, попадает в журнал Nginx.

```text
2026/10/21 15:09:26 [error] 156517#156517: *123 FastCGI sent in stderr: "PHP message: PHP Fatal error:  Uncaught Error: Call to undefined function mb_strlen() in /var/www/errors-demo-php/page.php:3
Stack trace:
#0 {main}
  thrown in /var/www/errors-demo-php/page.php on line 3" while reading response header from upstream, client: 127.0.0.1, server: example.com, request: "GET /page.php HTTP/1.1", upstream: "fastcgi://unix:/run/php/php8.2-fpm.sock:", host: "127.0.0.1:8096"
```

<figure><img src="https://docs.f3cloud.com/img/errors-500-php-log.png" alt="curl возвращает 500, в error.log запись FastCGI sent in stderr PHP Fatal error Call to undefined function mb_strlen()"><figcaption><p>Код 500 и строка из журнала с текстом ошибки PHP</p></figcaption></figure>

Запись начинается словами `FastCGI sent in stderr`, а после них идёт само сообщение PHP. Читать его нужно так. Тип ошибки `PHP Fatal error`, затем суть, `Call to undefined function mb_strlen()`, то есть вызвана функция, которой нет, и в конце файл и строка, где это произошло. В нашем примере функция `mb_strlen()` относится к модулю **mbstring**, который на свежей установке PHP отсутствует. Этот модуль входит в список, который мы советуем поставить для WordPress в статье про [установку Nginx](https://docs.f3cloud.com/ru/web/nginx-installation.md). Установите недостающий модуль.

```bash
# Устанавливаем модуль mbstring для PHP
apt install -y php-mbstring
```

После установки мы ничего не перезапускали, и страница сразу заработала.

<figure><img src="https://docs.f3cloud.com/img/errors-500-php-fixed.png" alt="После apt install php-mbstring страница возвращает 200 OK и число 11"><figcaption><p>Модуль установлен, страница возвращает 200 и результат работы функции</p></figcaption></figure>

Другая частая причина 500 в PHP, синтаксическая ошибка после правки файла. Проверить файл на сервере можно командой `php -l`, и она же скажет, в какой строке ошибка.

```bash
# Проверяем синтаксис файла
php -l /var/www/errors-demo-php/parse.php
```

```text
PHP Parse error:  syntax error, unexpected token "echo", expecting "," or ";" in parse.php on line 3
Errors parsing parse.php
```

> **Не включайте показ ошибок на рабочем сайте.**
>
> Параметр `display_errors` можно включить, чтобы увидеть ошибку прямо в браузере, но на рабочем сайте так делать не стоит. Текст ошибки содержит пути к файлам на сервере, и его увидит любой посетитель. Журнал Nginx даёт ту же информацию, но только вам.

### Цикл внутренних перенаправлений

Вторая причина не связана с приложением вовсе, это ошибка в настройках самого Nginx. Рассмотрим настройку, при которой для любого адреса, где нет подходящего файла, отдаётся главная страница `/index.html`.

```nginx
root /var/www/errors-demo-cycle;

location / {
    try_files $uri /index.html;
}
```

Последний параметр `try_files` работает как внутреннее перенаправление. Nginx начинает обработку заново уже для адреса `/index.html` и снова попадает в тот же `location`. Если файл `index.html` существует, всё заканчивается успешно. Если же его нет, например сборку сайта забыли загрузить или `root` указывает не туда, Nginx снова перенаправляет запрос на `/index.html`, и так по кругу. По документации Nginx такой цикл прерывается после десяти попыток, и клиент получает ошибку 500.

<figure><img src="https://docs.f3cloud.com/img/errors-500-cycle-browser.png" alt="Страница 500 Internal Server Error с надписью nginx/1.22.1"><figcaption><p>Здесь ошибку 500 нарисовал сам Nginx, поэтому внизу видна его версия</p></figcaption></figure>

В журнале запись говорит об этом прямо.

```text
2026/10/21 14:53:33 [error] 124894#124894: *11 rewrite or internal redirection cycle while internally redirecting to "/index.html", client: 127.0.0.1, server: example.com, request: "GET / HTTP/1.1", host: "example.com"
```

Фраза `rewrite or internal redirection cycle` означает циклическое перенаправление, а в кавычках стоит адрес, на который Nginx ходил по кругу. Проверьте, есть ли такой файл в папке, указанной в `root`, и при необходимости загрузите его.

```bash
# Смотрим, что лежит в папке сайта
ls -la /var/www/errors-demo-cycle

# Кладём файл на место
echo '<h1>Сайт работает</h1>' > /var/www/errors-demo-cycle/index.html

# Проверяем ответ
curl -sI http://127.0.0.1:8097/ | head -1
```

<figure><img src="https://docs.f3cloud.com/img/errors-500-cycle.png" alt="curl возвращает 500, в журнале rewrite or internal redirection cycle while internally redirecting to /index.html, после создания index.html ответ 200 OK"><figcaption><p>Цикл прекращается, как только нужный файл появляется в папке сайта</p></figcaption></figure>

После создания файла ответ стал `HTTP/1.1 200 OK`. Перезагружать Nginx для этого не потребовалось, ведь исправлять пришлось не настройки, а содержимое папки.

***

## Частые вопросы

<details>

<summary>upstream prematurely closed connection while reading response header from upstream</summary>

Эта строка в журнале сопровождает ошибку 502. Nginx подключился к приложению и отправил запрос, но приложение оборвало соединение, не прислав ответ. Например, оно могло упасть прямо во время обработки запроса. Посмотрите журнал службы командой `journalctl -u имя-службы -n 50 --no-pager`, там должна быть причина. Мы воспроизвели это строкой вида `upstream prematurely closed connection while reading response header from upstream, … request: "GET /close HTTP/1.1", upstream: "http://127.0.0.1:8098/close"`.

</details>

<details>

<summary>upstream sent too big header while reading response header from upstream</summary>

Ошибка 502 появляется, когда приложение присылает слишком длинные заголовки ответа, например огромные cookie. Nginx читает первую часть ответа в буфер, размер которого по умолчанию равен одной странице памяти, то есть 4 или 8 КБ. Если заголовки в него не помещаются, ответ считается недопустимым. Мы проверили это на заголовке длиной 9000 символов. Увеличьте буфер в блоке `location`.

```nginx
location / {
    proxy_pass http://127.0.0.1:8098;
    # Буфер для заголовков ответа побольше
    proxy_buffer_size 16k;
    proxy_buffers 8 16k;
}
```

Строку `proxy_buffers 8 16k;` пропускать нельзя. Когда мы добавили только `proxy_buffer_size 16k;`, `nginx -t` сообщил об ошибке `"proxy_busy_buffers_size" must be less than the size of all "proxy_buffers" minus one buffer`, и применять такие настройки нельзя. С обеими строками проверка прошла, а страница открылась с кодом 200.

</details>

<details>

<summary>nginx -t пишет [emerg] после правки настроек</summary>

Это значит, что в файле настроек ошибка, и Nginx не станет применять такую конфигурацию. Пока вы не выполните `systemctl reload nginx`, сайт продолжает работать по старым настройкам, поэтому всегда запускайте `nginx -t && systemctl reload nginx`, а не одну команду `reload`. Текст сообщения указывает, что именно не так. Например, `"proxy_busy_buffers_size" must be less than the size of all "proxy_buffers" minus one buffer` говорит о несогласованных размерах буферов в блоке `location`.

</details>

<details>

<summary>Сайт показывает ошибку, а в error.log пусто</summary>

Скорее всего, у этого сайта свой журнал ошибок. Он задаётся директивой `error_log` в блоке `server`, и тогда записи попадают в указанный файл, а не в общий `error.log`. Мы проверили это. С директивой `error_log /var/log/nginx/errors-demo.log;` в блоке сайта строка про ошибку 403 появилась только в этом файле. Найти все журналы, которые знает Nginx, можно командой ниже.

```bash
# Показываем все директивы error_log во всех настройках
nginx -T 2>/dev/null | grep -n error_log
```

В нашем примере команда вывела `error_log /var/log/nginx/error.log;` из основного файла и строку с адресом журнала сайта.

</details>

<details>

<summary>curl: (7) Failed to connect to … port … Couldn't connect to server</summary>

Это уже не одна из четырёх ошибок. Nginx не прислал вообще никакого ответа, потому что на этом порту никто не слушает. Браузер в таком случае показывает собственную страницу, у Chrome это `ERR_CONNECTION_REFUSED`. Мы получили такое сообщение при обращении к порту, который никто не занимал, `curl: (7) Failed to connect to 127.0.0.1 port 8090 after 0 ms: Couldn't connect to server`. Проверьте, что сам Nginx запущен, командой `systemctl status nginx`, и что порты 80 и 443 открыты в [брандмауэре](https://docs.f3cloud.com/ru/vps/security/firewall.md).

</details>

<details>

<summary>PHP-FPM в журнале пишет Maximum execution time of 30 seconds exceeded</summary>

Скрипт работал дольше, чем разрешает параметр `max_execution_time`, по умолчанию 30 секунд, и PHP его остановил. Nginx при этом возвращает 500, а не 504. Откройте `/etc/php/8.2/fpm/php.ini`, увеличьте значение параметра и перезапустите службу командой `systemctl restart php8.2-fpm`. Если скрипт должен работать дольше минуты, не забудьте поднять и `fastcgi_read_timeout` в настройках Nginx.

</details>

***

## Наши ресурсы

**Telegram-канал:** [https://t.me/f3cloud_news](https://t.me/f3cloud_news)\
**Официальный сайт:** [https://f3cloud.com](https://f3cloud.com/ru)
