이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.
- HTTP status code를 원인으로 확정하지 않고 병목 후보를 좁히는 방법은 무엇인가?
- 502, 503, 504는 각각 어떤 계층과 로그를 먼저 봐야 하는가?
- 4xx가 꼭 client 문제이고 5xx가 꼭 application 문제라는 오해를 어떻게 피하는가?
개요
HTTP status code는 장애 분석의 결론이 아니라 출발점이다. 403은 애플리케이션 권한 오류일 수도 있지만 WAF 차단일 수도 있고, 502는 app exception이 아니라 gateway와 upstream 사이의 연결/프로토콜 문제일 수 있다.
상태 코드를 제대로 쓰려면 반드시 세 가지를 함께 본다.
- 누가 응답을 만들었는가?
- 요청은 어느 계층 로그까지 남았는가?
- 실패는 즉시 발생했는가, timeout 뒤 발생했는가?
왜 백엔드 개발자가 알아야 하는가
백엔드 개발자는 status code를 설계하고 해석하는 사람이다. 그런데 운영 장애에서는 같은 status code가 여러 계층에서 만들어질 수 있다. 이 사실을 놓치면 잘못된 팀을 부르거나, 앱 코드만 보다가 LB/proxy/WAF 문제를 늦게 찾는다.
- WAF가 만든 403과 app authorization 403은 조치가 다르다.
- API Gateway가 만든 404와 Spring controller mapping 404는 확인 위치가 다르다.
- proxy가 만든 502와 application이 만든 500은 로그 위치가 다르다.
- 429는 retry 신호가 아니라 rate limit을 존중하라는 신호일 수 있다.
상태 코드는 “어디부터 볼지”를 정하는 라벨이지 “누가 잘못했는지”의 판결이 아니다.
요청 흐름에서의 위치
status code는 응답을 만든 계층에서 결정된다.
CDN/WAF
→ Load Balancer / Gateway
→ Reverse Proxy
→ Application
→ Downstream dependency따라서 같은 5xx라도 CDN edge에서 만들어졌는지, gateway에서 만들어졌는지, app에서 만들어졌는지에 따라 확인 로그가 달라진다.
원리
status code를 해석할 때는 class와 응답 주체를 함께 본다.
| class | 일반 의미 | 운영에서 주의할 점 |
|---|---|---|
| 2xx | 성공 | cache HIT이면 origin app은 실행되지 않았을 수 있음 |
| 3xx | redirect | redirect loop, http/https scheme, Host header 문제 가능 |
| 4xx | client 요청 문제 | WAF, auth proxy, gateway도 만들 수 있음 |
| 5xx | server 계열 실패 | app 밖의 gateway/proxy/LB도 만들 수 있음 |
Server, Via, X-Cache, Age, Location, Retry-After, X-Request-ID 같은 헤더는 응답 주체와 다음 확인 위치를 좁히는 단서다.
상태 코드별 병목 판단
| 상태 코드 | 흔한 의미 | 먼저 볼 계층 | 확인 포인트 |
|---|---|---|---|
| 400 | 요청 구문, binding 실패 | App, API Gateway | JSON, query, header size, schema |
| 401 | 인증 실패 | Auth filter, IdP, gateway | token 만료, cookie 미전송, challenge |
| 403 | 권한 또는 보안 차단 | App authz, WAF, IP policy | app auth log와 WAF deny log 분리 |
| 404 | 경로 없음 | Router, proxy rewrite, app mapping | prefix, rewrite, deployment path |
| 405 | method 불일치 | Router, CORS preflight | OPTIONS, allowed methods |
| 408 | client request timeout | client/proxy/server | 느린 upload, header/body 전송 지연 |
| 409 | 상태 충돌 | App, DB | optimistic lock, 중복 요청, idempotency |
| 413 | payload too large | CDN/proxy/app | body size limit, multipart config |
| 415 | media type 불일치 | App binding | Content-Type, message converter |
| 422 | 의미 검증 실패 | App validation | 필드는 맞지만 업무 규칙 위반 |
| 429 | rate limit | CDN/WAF/Gateway/App | limit key, Retry-After, retry 억제 |
| 500 | 내부 예외 | App | stack trace, recent deploy, dependency |
| 502 | bad gateway | Gateway/proxy/backend | upstream 연결, protocol, TLS, invalid response |
| 503 | service unavailable | LB/App/dependency | no healthy target, overload, maintenance |
| 504 | gateway timeout | Gateway/proxy/upstream | upstream response timeout, DB/API 지연 |
기존 HTTP 에러코드 문서의 핵심 흐름은 이 표와 아래 502/503/504 진단으로 흡수한다.
502 / 503 / 504 집중 비교
| 코드 | 질문 | 먼저 볼 증거 |
|---|---|---|
| 502 | gateway가 upstream에서 유효한 응답을 못 받았는가? | backend port, protocol mismatch, backend TLS, connection reset, invalid header |
| 503 | 처리 가능한 target이 없거나 과부하인가? | healthy target count, target draining, overload metric, maintenance flag |
| 504 | upstream 응답을 기다리다 timeout 났는가? | proxy read timeout, upstream response time, app trace, DB/API latency |
시간도 중요하다.
- 502가 즉시 반환되면 target health, protocol, TLS, port, listener rule을 먼저 본다.
- 503이 배포 직후 늘면 readiness, target draining, desired capacity, autoscaling을 본다.
- 504가 timeout 값과 비슷한 시간 뒤 나오면 app 처리, DB/API, proxy timeout 정렬을 본다.
로그 위치로 다시 좁히기
| 로그 위치 | 해석 |
|---|---|
| CDN/WAF log만 있음 | origin까지 가지 않고 edge에서 끝났을 수 있음 |
| LB/Gateway log는 있고 app log 없음 | target health, backend 연결, protocol, TLS 문제 가능 |
| Proxy access log는 있고 app log 없음 | upstream 전달 실패, rewrite, timeout 가능 |
| App access log는 있고 error log 없음 | app이 정상 응답했지만 앞/뒤 계층에서 변형 가능 |
| App error log 있음 | app exception, validation, auth, dependency 실패 가능 |
| Trace에서 DB/API span 지연 | app 내부보다 downstream 병목 가능 |
“app log 없음”은 앱이 정상이라는 증거가 아니라 요청이 app에 닿지 않았다는 단서일 수 있다.
실무에서 자주 만나는 문제
- 4xx를 무조건 client 잘못으로 본다.
- 403은 WAF, 404는 gateway route, 413은 proxy body limit일 수 있다.
- 응답 주체와 app log 도달 여부를 확인한다.
- 500과 502를 섞어 본다.
- 500은 app 내부 예외 가능성이 크고, 502는 gateway와 upstream 사이 문제가 더 흔하다.
- app error body와 gateway error body를 비교한다.
- 429에 즉시 retry한다.
- rate limit을 더 악화시킬 수 있다.
Retry-After와 limit key를 확인한다.
- 504를 timeout 값 없이 분석한다.
- 30초, 60초, 120초처럼 특정 값과 맞으면 proxy/LB timeout일 수 있다.
- proxy read timeout과 app dependency timeout을 표로 맞춘다.
디버깅 방법
기본 확인은 status, header, timing, 로그 위치를 함께 본다.
curl -vI https://example.com/api/orders
curl -sS -o /dev/null \
-w "code=%{http_code}\nremote_ip=%{remote_ip}\nfirst_byte=%{time_starttransfer}\ntotal=%{time_total}\n" \
https://example.com/api/ordersPOST나 body가 있는 요청은 재현 시 idempotency를 조심한다.
curl -X POST https://example.com/api/orders \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: debug-order-20260630-001' \
-d '{"productId": 10, "quantity": 1}'운영 로그에서는 gateway status와 target status를 분리해서 본다. ALB, Envoy, NGINX 모두 “사용자에게 반환한 status”와 “upstream이 반환한 status”가 다를 수 있다.
코드로 확인하기
애플리케이션은 에러 응답에 request id를 포함해야 한다.
{
"code": "ORDER_DEPENDENCY_TIMEOUT",
"message": "주문 처리 중 외부 결제 응답이 지연되고 있습니다.",
"requestId": "7f0f0d6a7c1a4b87"
}이런 응답은 사용자와 운영자가 같은 요청을 찾게 해 준다. 다만 내부 stack trace, token, 개인정보는 응답에 넣지 않는다.
주니어가 자주 하는 오해
- 5xx면 전부 서버 코드 문제라고 생각한다.
- 502/503/504는 app 밖에서 만들어지는 경우가 많다.
- 403이면 권한 로직만 본다.
- WAF, IP allowlist, CORS preflight 차단도 가능하다.
- 상태 코드 하나로 root cause를 말한다.
- root cause는 로그, metric, trace, 최근 변경과 함께 판단한다.
- status code를 많이 세분화하면 관측이 자동으로 좋아진다고 생각한다.
- request id, 응답 주체, error code, metric이 함께 있어야 한다.
시니어의 설계 판단 기준
- 에러 응답은 사용자 메시지, 운영 error code, request id를 분리한다.
- 4xx와 5xx metric은 응답 주체별로 나누어 본다.
- gateway status와 target status를 모두 dashboard에 올린다.
- 429와 503에는 retry 정책과
Retry-After의미를 명확히 한다. - API 문서에는 idempotency가 필요한 POST와 retry 가능 여부를 표시한다.
인프라 협업 포인트
- LB/Gateway 로그에서 client status, target status, target response time, chosen target을 확인한다.
- WAF/Rate limit 로그에서는 rule id, limit key, source IP, request path가 필요하다.
- Proxy 로그에는 upstream status, upstream response time, request id가 있어야 한다.
- 상태 코드 알림은 app status만이 아니라 edge/gateway/proxy layer별로 나누어야 한다.
실전 팁
- 장애방에는 status code만 쓰지 말고 response header와 request id를 함께 붙인다.
- 502/503/504는 즉시 실패인지 timeout 뒤 실패인지 시간이 중요하다.
- 400/422/409는 사용자 입력, 비즈니스 규칙, 동시성 충돌을 분리해서 설계하면 CS가 줄어든다.
- 개인 프로젝트에서도 NGINX
upstream_status를 남기면 502 분석 품질이 올라간다.
위험 신호!
- 모든 5xx가 하나의 alert로 묶여 있다.
- app log에는 200인데 gateway에서 5xx가 반환되는지 확인할 수 없다.
- 429 응답에 limit 기준과 retry 안내가 없다.
- 에러 응답에 request id가 없다.
- 상태 코드별 runbook이 없고 담당자 경험에 의존한다.
확인 질문
확인 질문
- 502와 504의 핵심 차이는 무엇인가?
- 502는 gateway가 upstream에서 유효한 응답을 못 받은 상황에 가깝고, 504는 upstream 응답을 기다리다 timeout이 난 상황에 가깝다.
- 403이 발생하면 어떤 두 로그를 나누어 봐야 하는가?
- 애플리케이션 authorization log와 WAF/IP policy deny log를 나누어 봐야 한다.
- status code를 root cause로 보면 안 되는 이유는 무엇인가?
- 여러 계층이 같은 status code를 만들 수 있고, status code는 다음 확인 위치를 정하는 단서일 뿐이기 때문이다.