이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.

  • 상태 코드를 원인 확정이 아니라 응답 주체와 다음 확인 계층을 좁히는 신호로 해석할 수 있는가?
  • 400/401/403/404/409/413/415/429/500/502/503/504를 운영 상황에서 어떻게 다르게 처리하는가?
  • alert, SLO, retry, user message, incident report에서 상태 코드를 어떻게 써야 하는가?

개요

HTTP status code는 원인 확정표가 아니라 운영 라우팅 신호다. 같은 403이라도 app 권한 정책, WAF 차단, IP allowlist, CSRF 실패일 수 있고, 같은 502라도 backend down, TLS mismatch, protocol mismatch, connection reset일 수 있다.

  • 상태 코드는 원인 확정이 아니라 병목 후보를 줄이는 신호다.
  • 응답 주체가 App인지 CDN인지 Gateway인지에 따라 같은 코드도 조치가 달라진다.
  • error body와 request id가 있어야 운영 분석이 빠르다.

왜 백엔드 개발자가 알아야 하는가

상태 코드를 잘못 해석하면 잘못된 팀을 깨우고, 잘못된 retry를 만들고, 잘못된 user message를 낸다. 백엔드는 status code를 HTTP 의미, app domain error, proxy/gateway log, SLO 지표와 함께 읽어야 한다.

  • 4xx가 늘어도 사용자 입력 오류인지, 인증 배포 문제인지, WAF 오탐인지 다르다.
  • 5xx가 늘어도 app bug인지, no healthy target인지, upstream timeout인지 다르다.
  • 429/503에는 Retry-After가 있어야 client가 부하를 더 키우지 않는다.
  • 499 같은 비표준 코드는 NGINX에서 client가 연결을 끊은 신호일 수 있다.
  • 304/204처럼 body가 없는 성공 응답을 에러로 오해하면 안 된다.

요청 흐름에서의 위치

status code는 응답이 만들어진 뒤 client에게 돌아오는 가장 압축된 신호다.

client request
→ CDN/WAF may return 403/429/5xx
→ LB/Gateway may return 502/503/504
→ app may return 2xx/3xx/4xx/5xx
→ client sees one status code

client가 본 하나의 status code만으로는 응답 주체를 알 수 없다. response header, body format, request id, 계층별 log가 필요하다.

원리

상태 코드 family는 넓은 의미를 준다.

Family의미운영 관점
1xx중간 정보100-continue, protocol 처리
2xx성공body 유무와 후속 행동 확인
3xxredirect/cacheLocation, loop, cache validation 확인
4xxclient/request 문제client 오류, auth, WAF, validation 구분
5xxserver/upstream 문제app, gateway, dependency, overload 구분

운영에서는 family보다 응답 주체와 재시도 가능성이 더 중요할 때가 많다.

핵심 판단 기준

  • status code, response body code, response header, request id를 함께 본다.
  • 4xx도 급증하면 incident일 수 있다. auth 설정, WAF rule, client SDK 배포 문제를 의심한다.
  • 5xx alert는 app-generated 5xx와 gateway-generated 5xx를 나눠야 한다.
  • retry는 status code만으로 결정하지 말고 method, idempotency, Retry-After, error code를 함께 본다.
  • SLO에서 제외할 4xx와 포함할 4xx를 명확히 정한다. 예를 들어 401 brute force와 404 crawler traffic은 서비스 실패와 다르게 볼 수 있다.

상태 코드별 병목 판단

상태 코드1차 의미먼저 볼 계층운영 판단
400요청 구문 또는 바인딩 실패Application, API GatewayJSON, query parameter, header 크기
401인증 필요 또는 인증 실패Auth filter, IdPtoken 만료, challenge, cookie 미전송
403권한 또는 보안 정책 차단App authz, WAF, IP allowlist권한 정책과 WAF deny log 분리
404경로 없음Router, proxy rewrite, app mapping배포 경로, prefix, rewrite 확인
405method 불일치Router, CORS preflight허용 method와 OPTIONS 처리
408client request timeoutClient, proxy, server느린 업로드, header/body 전송 지연
409상태 충돌Application, DB동시성, optimistic lock, 중복 요청
413payload too largeBrowser, proxy, appbody size limit, multipart 설정
415media type 불일치App bindingContent-Type과 message converter
422의미 검증 실패Application validation필드는 맞지만 비즈니스 규칙 위반
429rate limitCDN, WAF, API Gateway, appRetry-After, limit 단위, key 기준
500앱 내부 예외Applicationstack trace, recent deploy, dependency
502gateway가 upstream에서 정상 응답을 못 받음Gateway, proxy, backendbackend down, protocol mismatch, TLS
503서비스 일시 불가LB, app, dependencyoverload, maintenance, no healthy target
504gateway timeoutGateway to upstreamupstream 지연, read timeout, DB/API

상태 코드는 “어느 로그와 설정부터 볼지 정하는 라우팅 정보”로 사용한다.

로그 위치로 다시 좁히기

로그가 남은 위치의미다음 확인
어떤 로그에도 없음요청이 서비스 앞단까지 오지 않았을 수 있다.DNS, CDN, listener, 방화벽
CDN/WAF 로그만 있음origin까지 가지 않고 edge에서 처리되었을 수 있다.WAF rule, cache status, rate limit
Gateway 로그는 있고 App 로그는 없음Gateway와 backend 사이 문제일 수 있다.target health, backend port, protocol, TLS
Proxy 로그는 있고 App 로그는 없음upstream 전달 실패 가능성이 높다.NGINX/Envoy upstream, rewrite, timeout
App access log와 error log가 있음애플리케이션 처리 중 실패했다.stack trace, validation, auth, 최근 배포
App log는 있고 DB/API trace가 느림후단 의존성 병목일 수 있다.slow query, lock, pool, external API

502 / 503 / 504 비교

  • 502는 gateway가 upstream에서 유효한 HTTP 응답을 받지 못했다는 신호에 가깝다.
  • 503은 처리 가능한 healthy target이 없거나 overload, maintenance, rate limit 상황일 수 있다.
  • 504는 gateway가 upstream 응답을 기다리다가 timeout이 난 상황에 가깝다.
  • 같은 5xx라도 즉시 실패인지, 특정 시간 뒤 실패인지, App log가 있는지에 따라 의심 구간이 달라진다.

Alert와 SLO에서 쓰는 법

  • 5xx rate는 기본 alert가 될 수 있지만 502/503/504를 나누어야 담당 팀과 조치가 빨라진다.
  • 4xx는 무시하지 않는다. 401/403 급증은 인증 장애나 WAF 오탐일 수 있고, 429 급증은 capacity나 abuse 신호일 수 있다.
  • 499 같은 비표준 코드는 user abort, timeout, frontend route change, mobile network 문제를 추정하는 보조 신호다.
  • status code만으로 SLO를 만들면 client abuse와 서버 장애가 섞인다. route, caller, status family, response source를 label로 둔다.

CORS 오류는 상태 코드와 다르게 보기

브라우저 콘솔에 보이는 CORS 오류는 HTTP status code 자체가 아니라 브라우저가 응답 접근을 막은 결과일 수 있다. 서버가 200을 응답했더라도 Access-Control-Allow-Origin, Access-Control-Allow-Credentials, preflight OPTIONS 응답이 맞지 않으면 JavaScript는 응답을 읽지 못한다.

실무에서 자주 만나는 문제

  • 403을 무조건 권한 문제로 본다.
  • 502/503/504를 모두 서버 장애로 뭉갠다.
  • 301/302/304를 에러로 오해한다.
  • 429를 client 탓으로만 보고 capacity나 rate limit 정책 문제를 보지 않는다.
  • 500으로 모든 예외를 감싸 client가 재시도/수정 가능성을 구분하지 못한다.

상태 코드는 모니터링 label로도 쓰이므로 설계가 나쁘면 운영 판단이 흐려진다. 사용자 입력 오류를 500으로 보내면 장애율이 부풀고, 서버 실패를 200 success=false로 보내면 장애를 놓친다.

디버깅 방법

  • response header와 body format으로 응답 주체를 추정한다.
  • app access log 도달 여부를 확인한다.
  • 502/503/504는 실패까지 걸린 시간을 본다. 즉시 실패와 timeout 후 실패는 다르다.
  • 4xx 급증은 caller, user agent, route, WAF rule, auth provider deploy를 함께 본다.
  • 429/503은 Retry-After가 있는지 확인한다.

코드로 확인하기

curl -sS -o /tmp/body.txt -D /tmp/headers.txt \
  -w "code=%{http_code} remote_ip=%{remote_ip} first_byte=%{time_starttransfer} total=%{time_total}\n" \
  https://api.example.com/orders/123
 
cat /tmp/headers.txt
cat /tmp/body.txt

first_byte가 timeout 경계 근처이고 504라면 upstream read timeout을 의심한다. 즉시 502이고 app log가 없다면 gateway-backend 연결, protocol, TLS, target health를 먼저 본다.

주니어가 자주 하는 오해

  • 4xx는 전부 사용자 잘못이라고 생각한다.
    • WAF, auth provider, gateway 설정이 만든 4xx도 있다.
  • 5xx는 전부 app bug라고 생각한다.
    • LB, gateway, upstream timeout, no healthy target이 만든 5xx도 많다.
  • 304나 204를 실패로 본다.
    • body가 없을 수 있는 정상 응답이다.
  • 200이면 성공이라고만 생각한다.
    • body에 실패를 숨기는 API는 monitoring과 retry 판단을 망친다.

시니어의 설계 판단 기준

  • status code taxonomy를 API 표준으로 둔다.
  • route별 expected 4xx와 unexpected 4xx를 구분한다.
  • error body에는 domain code, retryable 여부, request id를 넣는다.
  • 429/503에는 가능하면 Retry-After나 rate limit header를 제공한다.
  • alert는 status family, exact code, response source, route, caller를 나눠 설계한다.

인프라 협업 포인트

  • gateway/LB가 만든 502/503/504와 app이 만든 5xx를 로그와 header로 구분한다.
  • WAF 403/429는 rule id, client IP, user agent, route를 함께 본다.
  • rate limit 정책은 key 기준, burst, refill, Retry-After를 앱 팀과 공유한다.
  • 인프라 팀은 같은 status code라도 어느 계층에서 만든 것인지가 없으면 추적 범위가 너무 넓어진다.

실전 팁

  • API 문서에는 성공 status뿐 아니라 대표 실패 status와 error code를 같이 쓴다.
  • dashboard는 4xx/5xx family만 보지 말고 exact code top-N을 둔다.
  • 401/403/404는 보안상 과도한 정보를 body에 넣지 않는다.
  • 5xx body에 내부 예외 class나 stack trace를 노출하지 않는다.

위험 신호!

  • 모든 실패를 200과 success=false로 보낸다.
  • validation 실패를 500으로 보낸다.
  • 502/503/504를 하나의 “서버 에러” alert로만 묶는다.
  • 429/503에 retry 정보를 주지 않는다.
  • status code와 error body code가 서로 모순된다.

확인 질문

확인 질문

  • 502와 504는 어떻게 다르게 해석해야 하는가?
    • 502는 gateway가 upstream에서 유효한 응답을 받지 못한 신호이고, 504는 upstream 응답을 기다리다 timeout이 난 신호에 가깝다.
  • 4xx 급증도 incident일 수 있는 이유는 무엇인가?
    • auth 설정 오류, WAF 오탐, client SDK 배포 문제, rate limit 정책 오류로 정상 사용자가 막힐 수 있기 때문이다.
  • 상태 코드만으로 원인을 확정하면 안 되는 이유는 무엇인가?
    • 같은 코드도 CDN/WAF/Gateway/App/Dependency 중 어디서 만들었는지에 따라 원인과 조치가 달라지기 때문이다.

참고 문서