Унификация страниц ошибок с помощью прокси-сервера Nginx

Унификация страниц ошибок с помощью прокси-сервера Nginx

1. Определение проблемы: несогласованные страницы ошибок и риски безопасности

В корпоративных веб-архитектурах и микросервисных архитектурах при возникновении ошибки отображаются разные страницы в зависимости от уровня, на котором произошла ошибка. В среде, где спереди работает обратный прокси Nginx, а за ним — приложение Spring Boot, встроенное в Tomcat, могут отображаться следующие разрозненные страницы.

  • Страница ошибки Spring Boot Whitelabel: Код состояния HTTP, временная метка и сообщение об ошибке отображаются в браузере в необработанном виде.

  • Стандартная страница ошибки Apache Tomcat: При возникновении ошибки 404/500 на уровне контейнера сервлетов появляется характерная градационная страница Tomcat.

  • Стандартная страница ошибки Nginx: Когда серверное приложение недоступно или истекает время ожидания, отображается грубая текстовая страница с сообщением 502 Bad Gateway / 504 Gateway Time-out.

Такая фрагментация не только ухудшает пользовательский опыт (UX), но и приводит к уязвимостям безопасности (CWE-209: раскрытие информации). Фингерпринтинг сервера, при котором раскрываются сведения о версии Tomcat или трассировки стека фреймворка, дает злоумышленникам подсказки для анализа уязвимостей системы.

image1.pngimage2.png

Страница ошибки Spring Boot

image3.png

Страница ошибки Apache Tomcat

image4.png

Страница ошибки Nginx

2. Технические ограничения: ограничения внутренней обработки Spring Boot

@ExceptionHandler или ErrorController в Spring Boot работает только тогда, когда процесс приложения (JVM/Tomcat) работает нормально и способен сформировать ответ.

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

  • Сбой JVM OOM (Out of Memory): процесс принудительно завершается из-за исчерпания памяти кучи (502 Bad Gateway)

  • Исчерпание пула потоков Tomcat / пула подключений к БД: Когда Tomcat не может установить новые подключения из-за массового трафика (504 Gateway Time-out)

  • Прерывание развертывания в рамках Rolling Update во время развертывания без простоя: Запросы, поступающие в короткий промежуток времени до запуска нового контейнера

Поскольку в таких ситуациях сбоя на уровне инфраструктуры код Spring Boot не может быть выполнен, отображается необработанная страница ошибки Nginx. Для обеспечения действительно отказоустойчивого управления ошибками их необходимо централизовать на уровне Nginx — первого шлюза для трафика.

3. Проектирование решения: централизованное управление на основе proxy_intercept_errors в Nginx

Ключевым механизмом превращения Nginx в контроллер ошибок переднего плана является директива proxy_intercept_errors. Если эта директива включена, Nginx перехватывает тело ответа, когда код HTTP-ответа, возвращенный Upstream (Tomcat/Spring Boot), равен 300 или выше, и внутри перенаправляет его на заранее определенный путь error_page.

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

  • Резервный режим на уровне инфраструктуры: Обеспечивает единообразную информационную страницу сервиса независимо от того, запущен ли Spring Boot.

  • Блокировка прямого доступа: Директива internal не позволяет пользователям напрямую обращаться к /error/500.html, вводя этот путь в адресной строке браузера.

4. Практический пример конфигурации nginx.conf для production-среды

Это основной пример конфигурации nginx.conf, который перехватывает ошибки Tomcat и Spring Boot и направляет их на пользовательские страницы ошибок.

# 1. [Content Negotiation] 요청 헤더(Accept) 기반 응답 포맷 판별
#   React 비동기 API 요청(JSON)과 일반 웹 브라우징(HTML)을 구분합니다.
map $http_accept $error_response_type {
  default "html";
  ~*application/json "json";
  ~*text/json "json";
}

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

    # 2. [핵심] 뒷단 WAS(Tomcat/Spring Boot)의 3xx/4xx/5xx 에러를 가로챕니다.
    proxy_intercept_errors on;

    # 3. HTTP 상태 코드별 내부 에러 디스패처로 라우팅
    error_page 404 = /handle_error_404;
    error_page 500 = /handle_error_500;
    error_page 502 = /handle_error_502;
    error_page 504 = /handle_error_504;

    # 기본 비즈니스 프록시 패스
    location / {
        proxy_pass http://backend_app;
        # WAS 장애 시 빠른 Nginx 에러 핸들러 전환을 위한 타임아웃
        proxy_connect_timeout 3s;
        proxy_read_timeout 10s;
    }

    # 4. [내부 디스패처] 헤더에 따라 JSON vs HTML 분기 반환
    #   internal 지시어로 외부 사용자의 직접 URL 호출을 차단합니다.
    location = /handle_error_500 {
        internal;
        if ($error_response_type = "json") {
            default_type application/json;
            return 500 '{"status": 500, "code": "INTERNAL_SERVER_ERROR", "message": "서버 오류가 발생했습니다."}';
        }
        rewrite ^ /error/500.html break;
    }

    # 5. [WAS 다운 대응] Spring Boot 프로세스 다운(OOM 등) 시 502 처리
    location = /handle_error_502 {
        internal;
        if ($error_response_type = "json") {
            default_type application/json;
            return 502 '{"status": 502, "code": "SERVICE_UNAVAILABLE", "message": "현재 서비스 점검 중입니다."}';
        }
        rewrite ^ /error/502.html break;
    }

    # 6. [정적 파일 격리] 커스텀 HTML 에러 페이지 서빙 (직접 접근 불가)
    location /error/ {
        internal;
        alias /usr/share/nginx/html/error/;
        add_header Cache-Control "no-store, no-cache, must-revalidate";
    }
}

Пример кода Nginx.conf

5. Расширенное проектирование: проблема белого экрана при интеграции с React и ее решение

Простое включение proxy_intercept_errors on; нормально работает для обычных веб-страниц, но вызывает критический сбой во время выполнения в среде React SPA.

  • Проблема (SyntaxError и белый экран):
    Когда серверная часть возвращает ошибку 500 или Tomcat отключается (502), пока React вызывает API с помощью axios/fetch, Nginx возвращает HTML-документ, указанный в error_page. Интерфейс пытается разобрать его как JSON, что вызывает SyntaxError: Unexpected token '<' и останавливает отображение всего экрана, оставляя его белым.

  • Решение (согласование содержимого в Nginx):
    Используйте модуль map в Nginx, чтобы определить значение Accept в заголовке запроса: направляйте обычные запросы просмотра (text/html) на HTML-страницу, а для асинхронных API-запросов React (application/json) немедленно возвращайте стандартизированную JSON-схему ошибки.

  • Улучшенные результаты:
    Даже когда Spring Boot полностью недоступен, глобальный Axios-интерцептор React может получить стандартизированный JSON-ответ и безопасно отобразить модальное окно или всплывающее уведомление «Сервер находится на техническом обслуживании», обеспечивая полноценный резервный режим на уровне инфраструктуры.

6. Заключение: надежность инфраструктуры благодаря разделению уровней

Управление ошибками на уровне прокси с использованием Nginx — это нечто большее, чем простая настройка дизайна; это базовая архитектура, разделяющая зоны ответственности инфраструктуры (Nginx) и приложения (Tomcat/Spring Boot) (Separation of Concerns).

Если приложение сосредоточено на чистой бизнес-логике и доменных исключениях, а прокси переднего плана перехватывает сбои инфраструктуры и простои и отвечает в стандартизированном формате, можно одновременно обеспечить стабильность и безопасность сервиса.

TK

Site footer