Pro Tips — 장애 대응·에러 페이지·디버깅
Nginx + Tomcat 운영 중 마주치는 실전 고수 팁입니다. 업스트림 장애 시 fallback 처리, 커스텀 에러 페이지, 헬스체크, 그리고 연동 문제 디버깅 방법을 다룹니다.
업스트림 장애 시 Fallback 처리
Tomcat이 다운되거나 오류를 반환할 때 사용자에게 의미 있는 응답을 제공합니다.
proxy_next_upstream — 자동 재시도
upstream tomcat_backend {
server 127.0.0.1:8080;
server 127.0.0.1:8081 backup; # 메인 장애 시 백업 인스턴스
keepalive 32;
}
server {
location / {
proxy_pass http://tomcat_backend;
# 업스트림 실패 시 다음 서버로 재시도
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 2; # 최대 재시도 횟수
proxy_next_upstream_timeout 10s; # 재시도 총 제한 시간
}
}
에러 인터셉트 — 커스텀 에러 페이지
server {
listen 443 ssl;
server_name example.com;
# 업스트림 에러를 Nginx가 가로채서 처리
proxy_intercept_errors on;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
}
# Tomcat이 5xx 반환할 때 커스텀 페이지
error_page 500 /error/500.html;
error_page 502 /error/502.html;
error_page 503 /error/503.html;
error_page 504 /error/504.html;
location /error/ {
root /var/www/myapp;
internal;
add_header Cache-Control "no-store";
}
# JSON API 에러 응답 (API 클라이언트용)
location /api/ {
proxy_pass http://127.0.0.1:8080;
error_page 500 502 503 504 @api_error;
}
location @api_error {
default_type application/json;
return 503 '{"error":"service_unavailable","message":"서비스가 일시적으로 점검 중입니다."}';
}
}
정적 fallback 파일 준비
<!-- /var/www/myapp/error/502.html -->
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta http-equiv="refresh" content="10"> <!-- 10초 후 자동 새로고침 -->
<title>서비스 점검 중</title>
<style>
body { font-family: sans-serif; text-align: center; padding: 50px; }
h1 { color: #e74c3c; }
</style>
</head>
<body>
<h1>서비스를 일시적으로 이용할 수 없습니다</h1>
<p>잠시 후 자동으로 새로고침됩니다. (10초)</p>
<p>문의: support@example.com</p>
</body>
</html>
헬스체크 설정
Nginx Upstream 헬스체크 (상용 Nginx Plus)
오픈소스 Nginx는 수동 헬스체크만 지원합니다. nginx-upstream-check-module 또는 proxy_next_upstream으로 장애를 감지합니다.
# 오픈소스 Nginx — 수동 헬스체크 엔드포인트
server {
listen 8888; # 내부 헬스체크 전용 포트
server_name localhost;
allow 127.0.0.1;
deny all;
location /health {
proxy_pass http://127.0.0.1:8080/actuator/health;
proxy_connect_timeout 3s;
proxy_read_timeout 5s;
access_log off;
}
}
Tomcat 헬스체크 엔드포인트 (Spring Boot)
# application.yml
management:
endpoints:
web:
exposure:
include: health
endpoint:
health:
show-details: never # 프로덕션: 상세 정보 숨김
# 외부 헬스체크 스크립트
#!/bin/bash
HEALTH=$(curl -sf http://localhost:8080/actuator/health | grep -c '"status":"UP"')
if [ "$HEALTH" -eq 0 ]; then
echo "Tomcat unhealthy — restarting"
systemctl restart tomcat
fi
연동 디버깅 — 문제 유형별 진단
502 Bad Gateway
# 1. Tomcat 포트 수신 확인
ss -tlnp | grep 8080
# 결과 없으면 Tomcat 미실행
# 2. Tomcat 직접 접근 테스트
curl -v http://localhost:8080/
# 응답 없으면 Tomcat 문제
# 3. Nginx → Tomcat 연결 테스트
curl -v --resolve example.com:443:127.0.0.1 https://example.com/
# X-Forwarded-For 헤더 확인
# 4. Nginx 에러 로그 확인
tail -50 /var/log/nginx/error.log
# "connect() failed (111: Connection refused)" → Tomcat 다운
# "upstream timed out" → Tomcat 응답 지연
504 Gateway Timeout
# Tomcat 처리 시간 확인 (access log의 마지막 필드: 응답시간 ms)
tail -100 /opt/tomcat/latest/logs/localhost_access_log*.txt | \
awk '{print $NF}' | sort -n | tail -10
# 상위 10개 느린 요청
# Nginx read_timeout 설정 확인 및 조정
grep "proxy_read_timeout" /etc/nginx/sites-available/myapp.conf
# Tomcat 스레드 상태 확인
curl -u admin:password http://localhost:8080/manager/status | \
grep -i "maxThreads\|currentThreadsBusy"
연결 reset / 끊김
# TIME_WAIT 과다 확인
ss -s | grep TIME-WAIT
# keepalive 불일치 확인
# Nginx keepalive_timeout vs Tomcat keepAliveTimeout
# Tomcat이 먼저 연결을 끊으면 Nginx는 502 반환
# 해결: Tomcat keepAliveTimeout을 Nginx keepalive_timeout보다 더 크게 설정
# Nginx: keepalive_timeout 65s;
# Tomcat: keepAliveTimeout="70000" (70초)
실전 디버깅 헤더 추가
개발/스테이징 환경에서 연동 상태를 쉽게 파악하기 위한 헤더를 추가합니다.
server {
# 응답에 디버깅 정보 추가 (개발 환경)
add_header X-Upstream-Addr $upstream_addr; # Tomcat 주소
add_header X-Upstream-Status $upstream_status; # Tomcat 상태코드
add_header X-Upstream-Time $upstream_response_time; # Tomcat 응답 시간
add_header X-Request-ID $request_id; # 요청 고유 ID
location / {
proxy_pass http://tomcat_backend;
# 요청 ID를 Tomcat에도 전달 (로그 연결)
proxy_set_header X-Request-ID $request_id;
}
}
로그 연결 — Nginx ↔ Tomcat 추적
# Nginx: X-Request-ID 생성 + 기록
log_format trace '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'req_id=$request_id '
'upstream_time=$upstream_response_time';
access_log /var/log/nginx/myapp_access.log trace;
// Spring Boot에서 X-Request-ID 로그에 포함
// logback-spring.xml 또는 MDC 설정
import org.slf4j.MDC;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
@Component
public class RequestIdFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
String requestId = ((HttpServletRequest) req).getHeader("X-Request-ID");
MDC.put("requestId", requestId != null ? requestId : "none");
try {
chain.doFilter(req, res);
} finally {
MDC.clear();
}
}
}
유지보수 모드 (점검 페이지)
# /etc/nginx/sites-available/myapp.conf
# 점검 모드 플래그 파일
# touch /var/www/maintenance.flag → 점검 모드 활성화
# rm /var/www/maintenance.flag → 점검 모드 해제
server {
listen 443 ssl;
server_name example.com;
location / {
# 점검 파일이 있으면 503 반환
if (-f /var/www/maintenance.flag) {
return 503;
}
proxy_pass http://tomcat_backend;
}
error_page 503 @maintenance;
location @maintenance {
root /var/www/myapp;
rewrite ^(.*)$ /error/maintenance.html break;
add_header Cache-Control "no-store";
add_header Retry-After 3600;
}
}
Nginx → Tomcat 연동 체크리스트
#!/bin/bash
echo "=== Nginx + Tomcat 연동 체크리스트 ==="
# 1. Nginx 설정 검증
echo -n "Nginx 설정: "
nginx -t 2>&1 | grep -q "successful" && echo "✅ OK" || echo "❌ 에러"
# 2. Nginx 실행 중
echo -n "Nginx 실행: "
systemctl is-active nginx > /dev/null && echo "✅ 실행 중" || echo "❌ 중지"
# 3. Tomcat 실행 중
echo -n "Tomcat 실행: "
systemctl is-active tomcat > /dev/null && echo "✅ 실행 중" || echo "❌ 중지"
# 4. Tomcat 포트 수신
echo -n "Tomcat :8080: "
ss -tlnp | grep -q ":8080" && echo "✅ 수신 중" || echo "❌ 미수신"
# 5. HTTP 응답
HTTP_CODE=$(curl -sk -o /dev/null -w "%{http_code}" https://localhost/)
echo "HTTP 상태: $HTTP_CODE (200 기대)"
# 6. 응답 시간
RESPONSE_TIME=$(curl -sk -o /dev/null -w "%{time_total}" https://localhost/)
echo "응답 시간: ${RESPONSE_TIME}s"
echo "=== 체크 완료 ==="
Summary
| 상황 | 해결책 |
|---|---|
| Tomcat 다운 | proxy_next_upstream error timeout + backup 서버 |
| 5xx 에러 페이지 | proxy_intercept_errors on + error_page |
| 502 발생 | Tomcat 수신 확인 → 직접 접근 테스트 → Nginx 로그 확인 |
| 504 발생 | proxy_read_timeout 증가 → Tomcat 처리 시간 분석 |
| keepalive 연결 끊김 | Tomcat keepAliveTimeout > Nginx keepalive_timeout |
| 점검 모드 | 플래그 파일 + if (-f) + 503 반환 |