РАЗРАБОТКА НА БИТРИКС

Создание страниц типовых ошибок (500, 404, 403 и т. д.) через nginx

Подписывайтесь на канал для bitrix-разработчиков в Telegram!

Введение

В данном кейсе будет рассмотрена настройка nginx для задания кастомных страниц типовых ошибок вместо стандартных или их отсутствия.

Описание задачи

Как правило, на наших проектах создаётся страница ошибки 404 в своём дизайне, которая подключается автоматически, с помощью штатной конфигурации сервера. А вот другим статусам ошибок такое внимание не уделяется, хотя реализовать подключение страниц для них несложно.
По итогу, вместо стандартного вывода браузера,
Создание страниц типовых ошибок
или стандартной страницы ошибки веб-сервера,
Стандартная страница ошибки веб-сервера
мы получим полноценную страницу сайта.
Полноценная страница сайта

Исходные данные

В качестве типового примера рассмотрим следующую конфигурацию. Предположим, для статуса 400 Bad Request нам нужно отдать статичный html-файл. Для статусов 401 Unauthorized, 402 Payment Required и 403 Forbidden будем использовать один php-обработчик, который сам должен будет определять тип ошибки. Для статуса 404 Not Found подключим другой php-обработчик. А для статусов 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable и 504 Gateway Timeout применим один общий html-файл.
Разбираемый пример затем можно будет адаптировать под потребности реального проекта.
Подключение PHP-страницы для обработки ошибки не рекомендуется — лучше подготовить и затем подключить специальные html-страницы без динамической информации. Генерируемые ботами ошибки (например, 404) через подключаемые страницы ошибок могут создавать существенную нагрузку на сервер.

Решение

Первым делом нужно определиться с тем, используется ли на сервере nginx. Уточнить это можно, проверив заголовки ответа сервера через Chrome DevTools.
Сервер nginx
Для задания страниц ошибок нужно внести правки в файл конфигурации nginx, который отвечает за настраиваемый сайт. Например, для наших серверов разработки путь к файлам конфигурации для сайтов содержит название сайта.
Пример для сайта site.o2k.ru — файл /etc/nginx/sites-available/site.o2k.ru.conf.
Для задания страниц ошибок в конфигурацию сервера нужно добавить следующие строчки:

fastcgi_intercept_errors on;
error_page 400 /error_pages/400.html;
error_page 401 /error_pages/40x.php?code=401;
error_page 402 /error_pages/40x.php?code=402;
error_page 403 /error_pages/40x.php?code=403;
error_page 404 /error_pages/404.php;
error_page 500 502 503 504 /error_pages/50x.html;
Общий вид файла конфигурации (красным фоном выделены изменения).

server {
        listen 80;
        set $root_path /home/bitrix/ext_www/site.o2k.ru;
        set $php_sock unix:/run/php/php7.4-fpm.sock;
        index index.php index.html index.htm;
        server_name site.o2k.ru;

        access_log /var/log/nginx/site.o2k.ru_access.log;
        error_log /var/log/nginx/site.o2k.ru_error.log error;

        include /etc/nginx/common.conf;
        include fastcgi_params;

        fastcgi_intercept_errors on;
        error_page 400 /error_pages/400.html;
        error_page 401 /error_pages/40x.php?code=401;
        error_page 402 /error_pages/40x.php?code=402;
        error_page 403 /error_pages/40x.php?code=403;
        error_page 404 /error_pages/404.php;
        error_page 500 502 503 504 /error_pages/50x.html;

        location ~ \.php$ {
            include snippets/fastcgi-php.conf;
            fastcgi_pass $php_sock;
        }
}
Смысл конфигурации в следующем:
  • fastcgi_intercept_errors on; отвечает за то, чтобы nginx перехватывал ошибки в PHP и обрабатывал их сам.
Без этой директивы ошибки, генерируемые в PHP (например, установка статуса 404), не будут обрабатываться nginx, и клиенту (браузеру) будет отдаваться «сырой» ответ от PHP.
При использовании Apache можно отключить эту директиву и отдавать страницы ошибок через него, но очевидных преимуществ у этого решения нет, а конфигурировать нужно уже два сервиса вместо одного.
Если эта директива установлена, то на указанных в директиве error_page страницах типовых ошибок (например, /error_pages/40x.php) не должен устанавливаться статус!
Это, например, вызов кода CHTTP::SetStatus("404 Not Found"); или header("HTTP/1.0 404 Not Found");. Если статус будет устанавливаться, то будет происходить зацикливание: PHP выдаёт статус 404, nginx перехватывает его и подключает свою страницу 404, на ней PHP выдает статус 404, nginx перехватывает его и так далее по кругу. Nginx отслеживает возникновение такой ситуации и выводит свою страницу ошибки по умолчанию, поэтому сервер не зависнет, но тем не менее наша кастомная страница ошибки не подключится.
  • error_page 500 502 503 504 /error_pages/50x.html; аналогично предыдущему, но таким образом можно в одну строку задать одну страницу для обработки нескольких статусов, приведя их список через пробел — корректный статус передаст сам nginx. Документация nginx: https://nginx.org/en/docs/http/ngx_http_core_module.html#error_page
После изменения конфигурации нужно перезагрузить nginx:

systemctl restart nginx
Для сверки можно использовать страницы, на которых устанавливаются соответствующие статусы. Такие страницы есть в архиве, приведённом в разделе «Файлы».

Примечание — обработка API-адресов

Если на сайте есть скрипты, работающие как API, которым требуется отдавать сообщение в определенном формате даже при ошибках, то конфигурацию нужно дорабатывать.
Пример: на сайте реализована интеграция с внешним сервисом. Его логика работы такова, что он отправляет запрос на сайт, а в ответе ожидает получить результат выполнение некой операции. Если же операцию выполнить невозможно, например, переданы неверные авторизационные данные, не найден запрошенный заказ и т. п., то сайт должен передать соответствующий HTTP-статус, а также сообщение в том же формате, что и при успешной обработке.
Однако по приведенной выше конфигурации nginx «перехватывает» ошибку и отдает специальную страницу. То есть сервису в ответе придет не сообщение в формате, например, JSON, а HTML-код типовой страницы ошибки. Чтобы это исправить, нужно добавить в конфигурацию отдельное правило для обработки адресов API.
Правило, которое нужно добавить, создаётся на основе правила обработки файлов .php. Достаточно скопировать его, добавить в условия директиву fastcgi_intercept_errors off; и изменить условие отбора страниц для применения этого правила.
Один из вариантов сформировать условие отбора — заменить ~ \.php$ () на ^~ /local/api/ (для раздела /local/api/ — заменить на актуальный). Первое — это отбор по регулярному выражению — все, что заканчивается на .php. Второе — это отбор по совпадению пути — все, что начинается с указанного пути. Символ ^ в начале правила означает, что правило применится сразу же, без поиска и применения других подходящих условий. Без этого переопределение директивы fastcgi_intercept_errors в правиле не срабатывает.
Пример итогового правила для раздела /local/api:

location ^~ /local/api/ {
    fastcgi_intercept_errors off;
    include snippets/fastcgi-php.conf;
    fastcgi_pass $php_sock;
}
Общий вид файла конфигурации (красным выделены изменения из предыдущего примера, синим — изменения из текущего раздела):

server {
        listen 80;
        set $root_path /home/bitrix/ext_www/site.spryskov.o2k.ru;
        set $php_sock unix:/run/php/php7.4-fpm.sock;
        index index.php index.html index.htm;
        server_name site.spryskov.o2k.ru;

        access_log /var/log/nginx/site.spryskov.o2k.ru_access.log;
        error_log /var/log/nginx/site.spryskov.o2k.ru_error.log error;

        include /etc/nginx/common.conf;
        include fastcgi_params;

				fastcgi_intercept_errors on;
				error_page 400 /error_pages/400.html;
				error_page 401 /error_pages/40x.php?code=401;
				error_page 402 /error_pages/40x.php?code=402;
				error_page 403 /error_pages/40x.php?code=403;
				error_page 404 /error_pages/404.php;
				error_page 500 502 503 504 /error_pages/50x.html;

				location ^~ /local/api/ {
				    fastcgi_intercept_errors off;
				    include snippets/fastcgi-php.conf;
			    	fastcgi_pass $php_sock;
				}

        location ~ \.php$ {
            include snippets/fastcgi-php.conf;
            fastcgi_pass $php_sock;
        }
}
После изменения конфигурации нужно перезагрузить nginx:

systemctl restart nginx
У такого решения есть недостаток — по новому правилу из указанного раздела интерпретатору PHP передаются все файлы, а не только файлы с разрешением .php. Поэтому особенно важно, чтобы указанный раздел не использовался для хранения загружаемых пользователями файлов.
Пример: в папку upload загружаются закачиваемые пользователями изображения. Если пользователь переименует файл с PHP-кодом example.php в файл изображения example.jpg, то сможет загрузить его, например, через интерфейс загрузки фото в форме отзыва. Если затем открыть этот файл по прямой ссылке, то он не будет исполнен интерпретатором PHP, поскольку ему передаются только файлы .php. А вот если такой файл будет загружен в указанный в конфигурации раздел (в примере это /local/api/), то его код будет исполнен.
Если кто-то получает доступ к файловой системе и загружает свои файлы не через интерфейсы сайта, а напрямую, то он может напрямую загрузить свой файл с кодом в любое место сайта и вызвать его исполнение по правилу .php. Поэтому потенциальная уязвимость решения именно в загружаемых пользователями файлах, которые загружаются через штатные интерфейсы. Маловероятно, что где-либо разделы для API будут использоваться еще и как разделы для хранения файлов пользователей, но теоретически такая возможность есть, поэтому о такой проблеме нужно знать.

Результат

Вместо стандартного браузерного сообщения об ошибке или стандартной страницы ошибки сервер, теперь на сайте выводится наша страница, содержимым которой мы можем управлять, в том числе используя в ее формировании код на PHP.
Файлы
Архив errors_pages_case.zip
Архив errors_pages_case.zip с папками error_pages и errors. В папке error_pages находятся простые примеры страниц ошибок, которые можно использовать как заглушки на время сверки настроек. В папке errors находятся страницы, устанавливающие соответствующие их названиям статусы. По этим страницам можно сверять, подключаются ли заданные в настройках сервера страницы ошибок, или выводятся стандартные. Также для сверки можно воспроизводить ситуацию, которую они и характеризуют, например, для проверки страницы статуса 404 набрать несуществующий адрес.
Подписывайтесь на канал для bitrix-разработчиков в Telegram!

Рекомендованные статьи