Пользовательские страницы ошибок Nginx: улучшение пользовательского опыта

Настройте полезные пользовательские страницы ошибок Nginx для ответов 404, 403 и 50x, не скрывая реальные сбои.

Пользовательские страницы ошибок Nginx: улучшение пользовательского опыта

Пользовательские страницы ошибок Nginx превращают сырой сбой в понятный следующий шаг. Они не исправляют битую ссылку, отсутствующий файл или упавшее приложение на апстриме. Они делают нечто меньшее, но всё же ценное: сообщают посетителю на простом языке, что произошло, и не дают ему почувствовать, что его выбросили с вашего сайта.

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

Лучшие страницы ошибок — это скучные операционные инструменты. Они статичны, быстры, доступны и честны. Они не скрывают сбои от мониторинга и не раскрывают внутренние детали пользователям.

Начните с ошибок, которые люди действительно видят

Вам не нужно разрабатывать страницу для каждого HTTP-статуса. Начните с распространённых.

404 Not Found — первая страница для настройки. Она появляется, когда запрошенный URL не соответствует файлу, маршруту или расположению Nginx, возвращающему контент. Старые ссылки, переименованные посты, удалённые страницы документации и вручную введённые URL — всё это ведёт сюда.

Полезная страница 404 говорит что-то вроде: «Мы не смогли найти эту страницу». Затем она предлагает путь обратно на главную, в индекс документации, в раздел продуктов или на страницу поиска. Не вините пользователя. URL мог быть неправильным задолго до того, как они по нему кликнули.

403 Forbidden — другое дело. Nginx понял запрос, но не будет его обслуживать. Причины включают права доступа к файлам, правила доступа, отключённый листинг каталогов, правила разрешения/запрета IP или требования аутентификации. Страница 403 должна быть спокойной и короткой. Если ресурс приватный, скажите об этом. Если пользователям может понадобиться доступ, укажите правильный путь для входа или поддержки.

Для сайтов на базе приложений аккуратно обрабатывайте ошибки 50x:

  • 500 Internal Server Error обычно означает, что приложение не смогло обработать запрос.
  • 502 Bad Gateway часто означает, что Nginx не получил корректного ответа от апстрим-сервиса.
  • 503 Service Unavailable полезен для обслуживания, перегрузки или намеренно недоступного сервиса.
  • 504 Gateway Timeout означает, что Nginx слишком долго ждал ответа от апстрима.

Пример панели управления SaaS: ваше приложение перезапускается во время развёртывания. На несколько секунд Nginx не может подключиться к апстриму, и пользователи видят 502. Пользовательская страница может сказать: «Панель управления временно недоступна. Пожалуйста, обновите страницу через минуту». Это не идеально, но понятнее, чем стандартная ошибка шлюза.

Создайте статические файлы ошибок

Храните страницы ошибок вне цепочки зависимостей приложения. Если база данных упала, страница 500 всё равно должна загружаться. Если приложение на Node, Python, Ruby или PHP нездорово, Nginx всё равно должен обслуживать статический запасной вариант.

Простая структура файлов может быть такой:

/var/www/example.com/public/
  index.html
  assets/
/var/www/example.com/errors/
  404.html
  403.html
  50x.html

Держите HTML лёгким. Избегайте сторонних скриптов, тяжёлых изображений, клиентских пакетов приложений и всего, что обращается к сломанному бэкенду. Небольшой CSS-файл подойдёт, если Nginx может обслуживать его напрямую.

Минимальный 404.html может быть таким:

<!doctype html>
<html lang="ru">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Страница не найдена</title>
</head>
<body>
  <main>
    <h1>Страница не найдена</h1>
    <p>Страница могла быть перемещена, или ссылка устарела.</p>
    <p><a href="/">Перейти на главную страницу</a></p>
  </main>
</body>
</html>

Этого достаточно. Вы можете стилизовать её под свой сайт, но сообщение и ссылки важнее украшений.

Подключите страницы с помощью error_page

В Nginx директива error_page сопоставляет один или несколько кодов статуса с URI. Базовый блок сервера выглядит так:

server {
    listen 80;
    server_name example.com www.example.com;

    root /var/www/example.com/public;

    error_page 404 /errors/404.html;
    error_page 403 /errors/403.html;
    error_page 500 502 503 504 /errors/50x.html;

    location /errors/ {
        internal;
        root /var/www/example.com;
    }

    location / {
        try_files $uri $uri/ =404;
    }
}

Разрешение путей — это та часть, которая сбивает с толку. В этом примере запросы к /errors/ используют root /var/www/example.com;, поэтому /errors/404.html сопоставляется с /var/www/example.com/errors/404.html.

Директива internal означает, что внешние клиенты не могут напрямую запросить /errors/404.html как обычный URL. Nginx всё ещё может обслуживать его внутренне при обработке ошибки.

После редактирования конфигурации протестируйте и перезагрузите:

sudo nginx -t
sudo systemctl reload nginx

Затем протестируйте поведение:

curl -I http://example.com/definitely-missing-page
curl -s http://example.com/definitely-missing-page | head

Статус всё ещё должен быть 404, даже если тело — ваш дружелюбный HTML. Распространённая ошибка — случайно вернуть 200 OK для отсутствующей страницы, что заставляет мониторинг и поисковые системы думать, что отсутствующий URL — это настоящая страница.

Пользовательские страницы за обратным прокси

Если Nginx проксирует запросы к апстрим-приложению, приложение может само возвращать свои ответы об ошибках. По умолчанию проксированные ответы обычно передаются клиенту. Чтобы Nginx перехватывал ответы об ошибках апстрима и использовал ваши правила error_page, включите proxy_intercept_errors в соответствующем контексте.

location /app/ {
    proxy_pass http://app_backend;
    proxy_intercept_errors on;

    error_page 502 503 504 /errors/50x.html;
}

Документация Nginx описывает proxy_intercept_errors как применяемый к проксированным ответам с кодами статуса больше или равными 300, которые затем могут быть перенаправлены в Nginx для обработки error_page. На практике не включайте его везде бездумно.

Для браузерных страниц перехват 502 или 503 часто полезен. Для JSON API это может быть неправильно. Клиенты API обычно ожидают структурированное тело ошибки в JSON, а не HTML-страницу. Вам могут понадобиться отдельные расположения:

location /api/ {
    proxy_pass http://api_backend;
    proxy_intercept_errors off;
}

location /dashboard/ {
    proxy_pass http://app_backend;
    proxy_intercept_errors on;
    error_page 502 503 504 /errors/50x.html;
}

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

Сохраняйте правильный код статуса

Nginx позволяет изменить код ответа с помощью error_page, но делайте это только когда это осмысленно. Это корректный синтаксис:

error_page 404 =200 /fallback.html;

Для большинства веб-сайтов это было бы плохой идеей. Отсутствующая страница должна оставаться 404. Поисковые системы, проверки доступности, аналитика и пользователи — все выигрывают от правды.

Есть законные случаи для изменения кодов, например, направление определённых ошибок в именованное расположение или возврат страницы обслуживания с 503. Но как правило по умолчанию сохраняйте исходный статус ошибки.

Для обслуживания вы можете быть явными:

location / {
    return 503;
}

error_page 503 /errors/maintenance.html;

location = /errors/maintenance.html {
    root /var/www/example.com;
    internal;
}

Если вы используете CDN или балансировщик нагрузки перед Nginx, помните, что у него может быть своё поведение со страницами ошибок. Решите, какой уровень отвечает за какие ошибки. Иначе вы можете тестировать Nginx напрямую и видеть одну страницу, в то время как пользователи за CDN видят другую.

Пишите страницы ошибок для людей

Содержимое должно быстро отвечать на три вопроса:

  • Что произошло?
  • Это временно?
  • Что я могу сделать дальше?

Для 404 полезные следующие шаги: поиск, главная страница, индекс документации или обращение в поддержку, если отсутствующая страница должна существовать. Для 503 полезные указания: повторить попытку позже или проверить страницу статуса. Для 403 укажите инструкции по входу или запросу доступа, если это уместно.

Избегайте стек-трейсов, имён хостов апстрима, путей файловой системы, версий пакетов, внутренних IP, идентификаторов запросов без объяснения и деталей инцидента. Идентификатор запроса может быть полезен, если служба поддержки может его использовать, но чётко маркируйте его:

<p>Если вы обращаетесь в поддержку, укажите этот идентификатор запроса: <code>$request_id</code></p>

Чтобы вставлять переменные, такие как $request_id, вам нужен шаблон конфигурации, который это поддерживает. Статические HTML-файлы не будут расширять переменные Nginx сами по себе. Многие команды оставляют публичные страницы ошибок статическими и полагаются на логи для идентификаторов запросов.

Доступность — часть полезности. Используйте один понятный h1, читаемый контраст, обычные ссылки и простой текст. Не делайте единственным действием восстановления крошечную иконку или кнопку, управляемую скриптом.

Тестируйте страницы намеренно

Не ждите, пока пользователи найдут ваши страницы ошибок. Тестируйте их после каждого изменения.

Для 404:

curl -i https://example.com/no-such-page

Для 403 создайте контролируемое тестовое расположение или используйте приватный тестовый файл. Не ослабляйте права доступа в продакшене только для того, чтобы вызвать ошибку.

Для 502 или 503 тестируйте в стейджинге, направив расположение на недоступный апстрим:

location /broken-upstream-test/ {
    proxy_pass http://127.0.0.1:59999;
    proxy_intercept_errors on;
    error_page 502 503 504 /errors/50x.html;
}

Затем запросите его и подтвердите как код статуса, так и тело:

curl -i https://staging.example.com/broken-upstream-test/

Также смотрите логи:

sudo tail -f /var/log/nginx/error.log /var/log/nginx/access.log

Хорошо выглядящая страница ошибки не должна стирать операционный сигнал. Ваши оповещения всё равно должны срабатывать, когда растёт частота 50x.

Пользовательские страницы ошибок Nginx — это небольшая задача по конфигурации с реальным влиянием на пользователей. Начните с 404, 403 и распространённых ошибок 50x. Обслуживайте статические файлы напрямую из Nginx. Сохраняйте точные коды статуса. Используйте proxy_intercept_errors только там, где имеют смысл HTML-запасные варианты. Затем тестируйте страницы так же, как вы тестируете любое другое поведение в продакшене.