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 или трассировки стека фреймворка, дает злоумышленникам подсказки для анализа уязвимостей системы.

Страница ошибки Spring Boot
Страница ошибки Apache Tomcat
Страница ошибки 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