본문 바로가기

Backend/Study

[Study] Nginx 리버스 프록시 설정 예제 (502/504 에러 해결)

반응형

 

0.  프록시 vs 리버스 프록시 – 개념 & 목적 비교

항목 프록시(Forward Proxy) 리버스 프록시(Reverse Proxy)
트래픽 방향 클라이언트 앞에 위치, 외부로 나가는 요청을 대리 서버 앞에 위치, 외부에서 들어오는 요청을 대리
주요 목적 내부 클라이언트 익명화, 접근 제어, 캐싱, DLP 로드밸런싱, SSL 종료, 캐싱, 보안(WAF), WAS 보호
외부에서 보이는 대상 프록시 뒤의 클라이언트는 숨겨짐 백엔드 서버(WAS)는 숨겨짐
대표 사용 예 사내망에서 외부 인터넷 접근 통제 NGINX/HAProxy로 여러 WAS에 분산
SEO/트래픽 영향 주로 내부 통제라 영향 적음 정적 자산 캐싱/압축, HTTP/2/3 종단으로 체감 성능↑

블로그 유입을 노린다면, “리버스 프록시로 성능·안정성·보안을 한 번에 잡는 방법”을 강조하세요. 🔥


1. NGINX 리버스 프록시 기본 설정(안정 템플릿)

아래 스니펫은 기본기를 담은 안전한 시작점입니다.

# /etc/nginx/nginx.conf (핵심만 발췌)
worker_processes auto;
events { worker_connections 10240; }

http {
  sendfile on;
  keepalive_timeout 65;
  client_max_body_size 16m;

  # 헤더가 큰 응답(세션/쿠키/토큰 등) 대비
  proxy_buffer_size       16k;
  proxy_buffers           8 16k;
  proxy_busy_buffers_size 32k;

  # 업스트림(백엔드) 정의
  upstream app_upstream {
    server 127.0.0.1:8080 max_fails=3 fail_timeout=10s;
    keepalive 64;
  }

  server {
    listen 80;
    server_name _;

    # 클라이언트 → NGINX → WAS 전달 헤더
    location / {
      proxy_pass http://app_upstream;
      proxy_http_version 1.1;
      proxy_set_header Connection "";
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto $scheme;

      # 타임아웃(504 방지/진단)
      proxy_connect_timeout   3s;   # TCP 연결
      proxy_send_timeout      30s;  # 요청 전송
      proxy_read_timeout      30s;  # 응답 수신

      # 재시도 정책(네트워크/일시 오류 완화)
      proxy_next_upstream error timeout invalid_header http_502 http_503 http_504;
      proxy_next_upstream_tries 2;
    }
  }
}

포인트: 502가 “Upstream header too big”일 수 있어 버퍼를 넉넉히. 504는 주로 응답 지연proxy_read_timeout/WAS 처리시간 튜닝 병행.


2. 502/504 원인 & 해결 체크리스트

502 Bad Gateway (게이트웨이가 잘못된 응답 수신)

  • 주요 원인
    • WAS 다운/Crash, 포트/주소 오타
    • WAS 응답 헤더가 너무 큼(쿠키 과다, Set-Cookie 다중)
    • 프로토콜 미스매치(HTTP/1.1 keep-alive 헤더/커넥션 처리 문제)
  • 해결 가이드
    • ✅ Upstream 연결 확인: curl -v http://127.0.0.1:8080/health
    • ✅ 버퍼 상향: proxy_buffer_size, proxy_buffers, proxy_busy_buffers_size
    • ✅ 헤더 정리: 불필요한 쿠키/헤더 축소, 압축/세션 전략 조정
    • ✅ keep-alive 정합: proxy_http_version 1.1, proxy_set_header Connection ""

504 Gateway Timeout (백엔드 응답 지연/타임아웃)

  • 주요 원인
    • WAS 처리 지연(쿼리 느림, 락, GC), 네트워크 지연
    • 타임아웃 값이 업무 특성에 비해 너무 짧음
  • 해결 가이드
    • proxy_read_timeout/proxy_send_timeout 합리화
    • ✅ WAS 스레드/DB 커넥션 풀 최적화(큐 길이·타임아웃 점검)
    • ✅ 장기 요청은 비동기/배치로 분리, API는 빠르게 ACK + 폴링/웹훅

3-1. NGINX에서 502/504를 HTTP 200 + JSON으로 커스터마이징

⚠️ 주의: 게이트웨이 레벨에서 상태코드를 200으로 바꾸면 모니터링/알람이 무뎌질 수 있습니다.
비즈니스 정책상 꼭 필요한 경우에만 사용하세요.
또한 WAS 자체가 다운이면 WAS에서 바꿀 수 없고 NGINX에서 가공해야 합니다.

방법 A) NGINX에서 직접 JSON 응답(정적 반환)

server {
  listen 80;
  server_name _;

  location / {
    proxy_pass http://app_upstream;
    proxy_intercept_errors on;           # NGINX가 에러 응답을 가로챔
  }

  # 502 → 200으로 바꾸고 JSON 바디 반환
  error_page 502 =200 @err502;
  location @err502 {
    default_type application/json;
    return 200 '{"errorCode":502,"message":"Bad Gateway","success":false}';
  }

  # 504 → 200으로 바꾸고 JSON 바디 반환
  error_page 504 =200 @err504;
  location @err504 {
    default_type application/json;
    return 200 '{"errorCode":504,"message":"Gateway Timeout","success":false}';
  }
}
  • 장점: WAS 다운이어도 동작, 구현 간단
  • 단점: 고정 메시지·현황 반영 한계(동적 컨텍스트 부족)

방법 B) 에러 시 내부 라우팅으로 동적 엔드포인트(백업 서비스/헬스) 호출

upstream app_upstream   { server 127.0.0.1:8080; }
upstream fallback_api   { server 127.0.0.1:9090; }  # 경량 백업/오류 핸들러

server {
  listen 80;
  proxy_intercept_errors on;

  location / {
    proxy_pass http://app_upstream;
  }

  # 502/504 발생 시 fallback API로 내부 재요청하되, 최종 상태코드는 200
  error_page 502 = @fallback_502;
  error_page 504 = @fallback_504;

  location @fallback_502 {
    proxy_pass http://fallback_api/error/502;
    # fallback 응답이 502라도 최종 200으로 덮어쓰기
    header_filter_by_lua_block { ngx.status = 200 }
  }

  location @fallback_504 {
    proxy_pass http://fallback_api/error/504;
    header_filter_by_lua_block { ngx.status = 200 }
  }
}
  • 장점: 동적 메시지/추적ID/시간 등 추가 가능
  • 단점: OpenResty(ngx_lua) 등 추가 모듈 필요, 복잡도 상승

3-2. WAS(Spring Boot)에서 “에러를 200 + JSON”으로 응답하기

이 방식은 WAS가 살아 있을 때 적용됩니다.
프록시에서 오는 502/504 자체는 WAS까지 도달하지 않으므로 NGINX 쪽 가공(위 A/B)이 필요합니다.
그래도 내부 예외(Timeout/DB 오류 등)를 200으로 래핑해야 하는 정책이 있다면 아래처럼 처리합니다.

예) @ControllerAdvice로 공통 예외를 200 + JSON으로 래핑

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(TimeoutException.class)
    public ResponseEntity<Map<String, Object>> handleTimeout(TimeoutException e) {
        Map<String, Object> body = new HashMap<>();
        body.put("errorCode", 504);
        body.put("message", "Gateway Timeout (wrapped)");
        body.put("success", false);
        return ResponseEntity.ok(body); // 항상 200 OK
    }

    @ExceptionHandler(UpstreamBadGatewayException.class)
    public ResponseEntity<Map<String, Object>> handleBadGateway(RuntimeException e) {
        Map<String, Object> body = new HashMap<>();
        body.put("errorCode", 502);
        body.put("message", "Bad Gateway (wrapped)");
        body.put("success", false);
        return ResponseEntity.ok(body);
    }
}
  • 장점: 응답 형식 통일, 클라이언트는 항상 200 처리 규약
  • 단점: 표준 HTTP와 어긋남(모니터링·중간 캐시·CDN 판단에 불리)

4. 운영 체크리스트

  • proxy_read_timeoutWAS 평균+피크 처리시간에 맞춤 (너무 짧으면 504, 너무 길면 연결 잠금)
  • 버퍼/헤더: 대형 쿠키·헤더가 있다면 proxy_buffer_size/proxy_buffers 상향
  • 재시도 정책: proxy_next_upstream을 과하게 키우면 중복 처리 위험 → 멱등(GET/HEAD) 위주 적용
  • 헬스체크: 간단한 /health 엔드포인트, WAS/DB 의존성 최소화
  • 관측성: $upstream_status, $request_time, $upstream_response_time접근로그에 포함
  • 에러 200 래핑 시: 별도 헤더(X-Error-Code)와 본문 필드(errorCode)를 일관 표준으로 운영

접근로그 예시 포맷(요약):
log_format main '$remote_addr "$request" $status $request_time $upstream_status $upstream_response_time';


5. 결론 & SEO 포인트

  • 핵심: NGINX 리버스 프록시는 성능·보안·안정성의 초석. 502/504는 연결/버퍼/타임아웃 3축으로 빠르게 진단.
  • 커스터마이징: 비즈니스 요구 시 502/504를 HTTP 200 + JSON으로 변환 가능(NGINX 또는 WAS).
  • SEO 팁: 본문에 자연스럽게 “Nginx reverse proxy 설정, 502 bad gateway 해결, 504 gateway timeout 해결” 키워드를 배치하고, 코드블록·표·체크리스트로 체류시간↑를 노리세요. ✅

6. 예제

1) 리버스 프록시 표준 템플릿(압축)

upstream app_upstream { server 127.0.0.1:8080 max_fails=3 fail_timeout=10s; keepalive 64; }
server {
  listen 80;
  location / {
    proxy_pass http://app_upstream;
    proxy_http_version 1.1; proxy_set_header Connection "";
    proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_connect_timeout 3s; proxy_send_timeout 30s; proxy_read_timeout 30s;
    proxy_buffer_size 16k; proxy_buffers 8 16k; proxy_busy_buffers_size 32k;
    proxy_next_upstream error timeout invalid_header http_502 http_503 http_504;
    proxy_next_upstream_tries 2;
    proxy_intercept_errors on;
  }
  error_page 502 =200 @err502;
  location @err502 { default_type application/json; return 200 '{"errorCode":502,"message":"Bad Gateway","success":false}'; }
  error_page 504 =200 @err504;
  location @err504 { default_type application/json; return 200 '{"errorCode":504,"message":"Gateway Timeout","success":false}'; }
}

 

2) Spring Boot 예외 래핑(간단 버전)

@RestControllerAdvice
class GlobalHandler {
  @ExceptionHandler({TimeoutException.class})
  ResponseEntity<Map<String,Object>> t(TimeoutException e){
    return ResponseEntity.ok(Map.of("errorCode",504,"message","Gateway Timeout","success",false));
  }
  @ExceptionHandler({UpstreamBadGatewayException.class})
  ResponseEntity<Map<String,Object>> b(RuntimeException e){
    return ResponseEntity.ok(Map.of("errorCode",502,"message","Bad Gateway","success",false));
  }
}
반응형