본문으로 건너뛰기
Advertisement

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 반환
Advertisement