화면에는 그냥 서버 오류처럼 보이죠? 사실은 그 응답을 만든 사람이 앱 서버가 아닐 수도 있어요.Proxy, Reverse Proxy, 그리고 Load Balancer에서는 사용자가 보는 서버 앞에 리버스 프록시나 로드 밸런서가 설 수 있다는 큰 그림을 봤어요. 그리고 End-to-End Request Debugging에서는 요청 하나를 DNS, 연결, TLS, 프록시, 캐시, 오리진 체크포인트로 나눠 읽었죠. 이번 글은 그중에서도 서버 앞단과 오리진 사이에서 보이는 5xx를 읽어볼게요. 운영 중에 이런 화면을 보면 마음이 급해져요.
“이 응답은 누구의 목소리일까요?”HTTP 상태 코드의 기준 의미는 RFC 9110에 정리돼 있어요. 이 글에서는 그 표준 의미를 바닥에 두고, 실제 디버깅에서는 어떤 신호를 같이 봐야 하는지에 집중할게요.
여기서는 502, 503, 504를 프록시, 로드 밸런서, CDN, 오리진 경계에서 읽는 법에 집중해요. 제품마다 에러 페이지 문구, 헤더 이름, 타임아웃 정책은 달라요. 그래서 특정 벤더의 규칙처럼 외우기보다, 어느 지점의 응답인지 좁히는 읽기 순서를 잡는 게 목표예요.
같은 5xx라도 말하는 위치가 달라요
큰 물류센터에 주문을 넣는다고 해볼게요.- 고객은 대표 창구에 주문해요.
- 대표 창구는 뒤쪽 창고나 담당 부서에 다시 물어봐요.
- 뒤쪽이 이상하면 대표 창구가 대신 사과문을 줄 수도 있어요.
- 또는 뒤쪽 부서가 직접 “지금은 처리 불가”라고 답할 수도 있죠.
그래서 5xx를 볼 때는 숫자 하나만 보지 말고, 그 숫자가 어느 위치에서 만들어졌는지를 같이 봐야 해요.
이 그림에서 중요한 건, 브라우저가 받은 응답이 항상 오리진 앱 서버에서 나온 건 아니라는 점이에요. 중간에 있는 엣지나 프록시가 뒤쪽과 대화하다가 실패해서 직접 응답할 수도 있어요.
502, 503, 504는 이렇게 갈라서 봐요
먼저 세 숫자의 기본 감각부터 잡아볼게요.
RFC 9110 기준으로도 502는 gateway나 proxy가 upstream에서 잘못된 응답을 받았을 때, 503은 서버가 일시적인 과부하나 점검으로 처리하지 못할 때, 504는 gateway나 proxy가 upstream에서 제때 응답을 받지 못했을 때 쓰는 상태 코드예요. 여기서
503의 의미는 지금 처리할 수 없다는 것이지, 실제 응답 생성자가 앱 서버라는 뜻은 아니에요. Retry-After 헤더가 함께 있을 때는 재시도까지 얼마나 기다리면 좋을지 알려주는 힌트로 읽을 수 있어요.
하지만 이 표만 외우면 아직 부족해요. 실제 운영에서는 앞단이 503을 만들 수도 있고, 오리진 앱이 503을 직접 만들 수도 있거든요. 그래서 다음 질문이 필요해요.
“이 코드는 의미상 무엇이고, 이 응답은 어느 장비가 만들었을까요?”
먼저 응답을 만든 쪽을 찾아요
curl -v나 브라우저 Network 탭에서 응답 헤더를 보면 이런 단서들이 나올 수 있어요.
경로 위에서 어디가 실패했는지 나눠봐요
서버 앞단 오류는 보통 앞단이 뒤쪽과 말하는 과정에서 드러나요. 같은503이라도 두 장면이 있어요. 앞단이 “지금 뒤쪽으로 보낼 수 없어요”라고 말할 수도 있고, 오리진 앱이 “지금 점검 중이에요”라고 말한 응답을 앞단이 그대로 전달할 수도 있어요.
그래서 로그도 한 곳만 보면 안 돼요.
502는 “뒤쪽 응답을 제대로 못 읽었다”에 가까워요
502 Bad Gateway는 이름 때문에 gateway가 핵심이에요. 브라우저가 직접 오리진과 말한 게 아니라, 중간의 gateway나 proxy가 뒤쪽 upstream과 말하다가 정상적인 응답으로 처리할 수 없는 상황을 만난 거죠.
흔한 장면은 이런 식이에요.
여기서 중요한 건, 502가 항상 앱 코드 예외라는 뜻은 아니라는 점이에요. 앱이 죽어서 생길 수도 있지만, 앞단과 오리진 사이의 연결 설정, 프로토콜, keep-alive, health check 문제일 수도 있어요.
이 흐름은 정답표가 아니라 시작점이에요. 502를 보면 “앱 서버가 나쁜 응답을 줬다”로만 좁히지 말고, 앞단이 뒤쪽 응답을 어떻게 읽다가 실패했는지를 봐야 해요.
503은 “지금은 받을 수 없다”에 가까워요
503 Service Unavailable은 말 그대로 지금 서비스가 요청을 처리할 수 없다는 신호예요. 표준 의미로는 일시적인 과부하나 예정된 점검 같은 장면을 포함해요.
여기서 Retry-After가 붙으면 읽기가 조금 쉬워져요.
Retry-After가 있을 때도 성공을 보장하는 약속은 아니고, 없다고 해서 즉시 재시도해도 된다는 뜻도 아니에요.
하지만 503은 만든 주체가 특히 중요해요.
그러니까 503을 보면
Retry-After만 볼 게 아니라, 서버 풀이 비었는지, 점검 모드인지, 앞단 정책이 막았는지를 같이 봐야 해요.
504는 “기다렸는데 시간이 끝났다”에 가까워요
504 Gateway Timeout은 앞단이 뒤쪽 upstream에 요청했지만, 정해진 시간 안에 응답을 받지 못한 장면이에요.
브라우저 waterfall에서는 이런 느낌으로 보일 수 있어요.
Waiting for server response가 거의 30초라면, 브라우저 입장에서는 첫 바이트를 오래 기다렸다는 뜻이에요.
물론 이 숫자만으로 “DB가 느리다”까지 단정할 수는 없어요. 하지만 다음 질문은 정해져요.
504는 사용자가 보는 숫자보다 시간 모양이 더 중요할 때가 많아요. 항상 30초, 60초처럼 특정 값 근처에서 끊긴다면, 그건 사람의 체감 시간이 아니라 설정된 timeout의 흔적일 수 있어요.
curl과 waterfall에서는 이렇게 나눠 읽어요
앞 글에서 본 curl verbose와 timing, 브라우저 waterfall을 여기에 붙이면 더 실전적으로 읽을 수 있어요.curl에서는 상태 줄과 헤더를 먼저 봐요
server나 via는 절대적인 증거라기보다 출발점이에요. 헤더는 숨겨질 수 있고, 앞단이 오리진 헤더를 그대로 전달할 수도 있어요. 그래도 request id가 있으면 로그를 이어 붙일 수 있어요.
waterfall에서는 실패까지 걸린 시간을 봐요
Waiting이 길다고 바로 앱 코드가 느리다고 단정할 수는 없어요. 그 안에는 프록시 대기, 오리진 처리, 내부 API, DB, queue 대기가 섞일 수 있어요. 대신 시간 모양은 다음에 볼 로그의 위치를 정하는 데 아주 좋아요.로그를 볼 때는 같은 요청을 이어 붙여요
502/503/504 디버깅에서 자주 놓치는 건 같은 요청을 보고 있지 않은 로그들을 비교하는 실수예요. 가능하면 아래 값을 같이 모아요.
특히 request id는 하나만 있다고 끝이 아니에요. CDN request id, 프록시 request id, 앱 trace id가 서로 다를 수 있어요. 앞단에서 앱으로 id를 넘겨주도록 구성해두면 훨씬 읽기 쉬워져요.
자주 헷갈리는 장면을 나눠볼게요
이 표에서 계속 반복되는 핵심은 하나예요. 상태 코드와 로그를 같은 경로 위에 올려놓고 읽어야 한다는 점이에요.
잘못 읽기 쉬운 함정
모든 5xx를 앱 버그로 보기
5xx는 서버 쪽 계열 오류지만, “항상 앱 코드 예외”라는 뜻은 아니에요. CDN, 프록시, 로드 밸런서, 오리진, 내부 API 중 어디서든 만들어질 수 있어요.502, 503, 504를 그냥 같은 장애로 묶기
셋 다 사용자에게는 실패처럼 보이지만 읽는 질문이 달라요. 502는 앞단이 뒤쪽 응답을 제대로 못 읽은 쪽, 503은 지금 처리 불가, 504는 기다렸지만 시간 초과 쪽으로 먼저 나눠요.에러 페이지 디자인만 보고 출처를 확정하기
앞단이 앱 스타일의 커스텀 에러 페이지를 줄 수도 있고, 앱이 앞단처럼 보이는 단순 HTML을 줄 수도 있어요. 본문 디자인은 단서지만, 헤더와 로그 없이 확정하면 위험해요.Retry-After가 없으니 재시도하면 된다고 보기
Retry-After가 없다는 건 “지금 바로 무한 재시도해도 된다”가 아니에요. 재시도는 요청의 안전성, 멱등성, 서버 상태, 백오프 정책과 같이 봐야 해요.
앱 로그에 없으니 요청이 없었다고 보기
앱 로그에 없다는 건 정말 요청이 없었다는 뜻일 수도 있지만, 앞단에서 막혔거나 다른 target으로 갔거나 로그 샘플링에서 빠졌다는 뜻일 수도 있어요. 그래서 엣지와 프록시 로그를 같이 봐야 해요.자, 정리해볼까요?
502,503,504는 모두 서버 쪽 실패처럼 보이지만, 응답을 만든 계층이 다를 수 있어요.502 Bad Gateway는 앞단이 뒤쪽 upstream에서 유효한 응답을 받지 못한 장면으로 먼저 읽어요.503 Service Unavailable은 지금 처리할 수 없다는 의미이지, 앱이 직접 만든 응답이라는 뜻은 아니에요. 과부하, 점검, 서버 풀 없음, 앞단 정책이 모두 후보가 될 수 있어요.504 Gateway Timeout은 앞단이 뒤쪽 응답을 제시간에 받지 못한 장면으로 먼저 읽어요.- 응답 헤더의
Server,Via,Retry-After, request id, 에러 본문, waterfall 시간 모양을 같이 보면 원인 위치를 더 좁힐 수 있어요. - 숫자를 보자마자 원인을 맞히려 하지 말고, 먼저 “누가 이 응답을 만들었나?” 를 물어보는 게 좋아요.
이어서 보면 좋은 글
- Proxy, Reverse Proxy, 그리고 Load Balancer - 서버 앞단이 왜 요청을 먼저 받는지 큰 그림을 다시 볼 수 있어요.
- End-to-End Request Debugging - 요청 하나를 DNS, TLS, 프록시, 캐시, 오리진 체크포인트로 이어서 볼 수 있어요.
- curl verbose와 timing은 어디부터 읽어야 할까요? - 상태 줄, 응답 헤더, timing 값을 터미널에서 직접 나눠 읽어봐요.
- 브라우저 waterfall은 어디부터 읽어야 할까요? -
Waiting for server response와 전체 요청 시간을 브라우저에서 나눠 읽어봐요.