HTTP 상태 코드는 매 요청에 대한 서버의 한 줄 응답이다. 코드 하나가 클라이언트가 다음에 할 행동을 결정한다 — 리트라이, 로그인 페이지로 이동, 오류 표시, 캐시 사용. 이 가이드는 RFC 9110 (Semantics) 의 표준 코드 중 실제로 자주 쓰이는 것만 골라 정리한다. 코드별 단어 정의 위주가 아니라 실무에서 언제 어느 코드를 골라야 하는가 에 집중한다.
다섯 가지 분류 — 100~500 의 뜻
| 범위 | 분류 | 의미 |
|---|---|---|
| 1xx | Informational | 요청 받음, 처리 중 (거의 안 보임) |
| 2xx | Success | 요청 성공 |
| 3xx | Redirection | 다른 곳을 다시 요청해야 함 |
| 4xx | Client Error | 클라이언트 잘못 — 요청을 고쳐서 다시 보내야 함 |
| 5xx | Server Error | 서버 잘못 — 클라이언트가 고칠 수 없음 (리트라이 정책 필요) |
전체 코드 표를 빠르게 찾을 때는 HTTP 상태 코드 가 가장 빠르다 — 코드 번호 또는 키워드로 검색.
가장 자주 쓰는 2xx
- 200 OK — 일반 성공. GET, PUT, POST (응답 본문 있음).
- 201 Created — 새 리소스 생성됨. POST 응답으로 권장. 응답 헤더
Location에 새 리소스 URL. - 202 Accepted — 요청 받았지만 처리는 비동기. 아직 결과 없음. job ID 와 polling URL 을 보내는 패턴.
- 204 No Content — 성공했지만 본문 없음. DELETE 또는 PUT (idempotent) 응답으로 적합.
3xx — 흔히 헷갈리는 리다이렉트
- 301 Moved Permanently — 영구 이동. 브라우저·검색엔진이 캐시·인덱스를 갱신. 슬러그 변경 시 사용. 주의: 일부 오래된 클라이언트는 POST → GET 으로 method 를 바꿔 다시 보낸다. 그래서 API 에는 308 권장.
- 302 Found — 임시 이동. 위와 같은 method 변경 문제.
- 303 See Other — POST/PUT 후 GET 으로 결과 페이지로 보내고 싶을 때. 명시적으로 method 를 GET 으로 바꾼다.
- 304 Not Modified — 캐시된 버전을 그대로 쓰라는 응답.
If-None-Match/If-Modified-Since헤더에 반응. - 307 Temporary Redirect — 임시 이동. method 변경 X.
- 308 Permanent Redirect — 영구 이동. method 변경 X. API 엔드포인트 이전 시 권장.
4xx — 클라이언트 에러
400 vs 422 — 형식 vs 의미
- 400 Bad Request — 요청 자체가 망가짐. JSON 파싱 실패, 필수 헤더 누락, 쿼리 파라미터 형식 오류. 서버가 본문을 이해할 수 없는 상태.
- 422 Unprocessable Content — 본문은 파싱 됐는데 비즈 니스 규칙을 위반. 예: email 형식은 맞지만 이미 가입된 주소, 가격이 음수. WebDAV 출신이지만 REST API 에서 표준으로 자리잡음 (Rails, Symfony, Stripe 등).
선택 기준: 본문을 파싱할 수 있었는가? 못 했으면 400, 파싱은 됐는데 비즈니스 검증에 실패했으면 422.
401 vs 403 — 누구인지 vs 무엇을 할 수 있는지
- 401 Unauthorized — 인증 (authentication) 실패. 토큰이 없거나, 만료됐거나, 위조됨. 의미적으로는 "누구인지 모르겠다". 클라이언트는 로그인 페이지로 보내야 함. 응답 헤더
WWW-Authenticate권장. - 403 Forbidden — 권한 (authorization) 실패. 누구인지는 알지만 이 리소스에 접근할 권한이 없음. 의미적으로 "넌 알겠는데, 이건 못 본다". 클라이언트는 권한 요청 또는 관리자 문의 화면을 보여야 함.
예: 로그인 안 한 상태에서 /admin 접근 = 401. 로그인 했지만 일반 사용자가 /admin 접근 = 403.
그 외 자주 쓰는 4xx
- 404 Not Found — 리소스 없음. URL path 자체가 잘못됐을 수도 있고, 리소스 ID 가 존재하지 않을 수도 있음. 보안상 403 대신 404 를 쓰는 패턴 (리소스 존재 자체를 숨김) — Stripe, GitHub.
- 405 Method Not Allowed — URL 은 맞는데 method 가 틀림 (GET 만 받는 곳에 POST). 응답 헤더
Allow에 허용된 method 를 박는다. - 409 Conflict — 동시성 충돌. ETag mismatch, 이미 같은 이름이 존재함. 클라이언트는 최신 상태를 다시 받아서 재시도해야 함.
- 410 Gone — 영구 삭제됨. 404 와 다르게 "있었는데 이제 없다" 가 명시. 검색엔진이 더 빨리 인덱스에서 빼는다.
- 413 Payload Too Large — 본문이 서버 제한 초과. 파일 업로드 한도 등.
- 415 Unsupported Media Type —
Content-Type이 지원되지 않음. JSON 만 받는 곳에 XML 보냈을 때. - 429 Too Many Requests — 레이트 리밋 초과. 응답 헤더
Retry-After에 대기 초/날짜.
5xx — 서버 에러
- 500 Internal Server Error — 분류할 수 없는 서버 오류. 예상 못 한 예외가 잡혔을 때. 본문에 stack trace 노출 금지 (정보 노출).
- 501 Not Implemented — method 자체를 지원하지 않음. 예: 서버가 PATCH 를 구현하지 않은 상태. 405 와 다른 점: "이 서버는 그 method 를 영영 처리하지 않음".
- 502 Bad Gateway — 프록시/게이트웨이가 업스트림으로부 터 잘못된 응답 받음. Cloudflare, Nginx 의 흔한 화면.
- 503 Service Unavailable — 일시적으로 사용 불가 (정비, 과부하).
Retry-After헤더 권장. - 504 Gateway Timeout — 업스트림이 응답 시간 안에 답을 못 줌. 클라이언트는 백오프 후 재시도.
리트라이 가능 vs 불가능
클라이언트가 자동 재시도해도 되는 코드와 절대 안 되는 코드를 구분해야 한다.
- 재시도 권장: 429 (Retry-After 만큼 대기), 502/503/504 (지수 백오프 + jitter).
- 재시도 금지: 400, 401, 403, 404, 422 — 같은 요청을 다시 보내도 결과 같음. 코드를 고치거나 사용자 입력을 수정해야 함.
- idempotent method 만 자동 재시도 — GET·PUT·DELETE 는 안전. POST 는 idempotency key 가 없으면 중복 처리 위험.
API 설계 권장
- 일관된 코드 선택 — 같은 종류의 실패에 매번 같은 코드. 팀 안에서 "비즈니스 검증 실패 = 422" 처럼 합의.
- 응답 본문에 에러 코드 — HTTP 코드는 굵은 분류, 본문 JSON 의
error_code필드로 세부 사유 (예:email_already_taken). 클라이언트가 이 필드로 분기. - 3xx 는 신중히 — API 클라이언트가 자동으로 따라가지 않으면 깨진다. REST 응답으로는 거의 안 쓰고 OAuth 흐름에서 주로.
- 200 으로 에러 보내지 않기 — 본문에 "success: false" 를 박고 200 을 보내는 사례가 종종 있는데, HTTP 인프라(프록시·캐시·CDN) 가 본문을 보지 않으므로 잘못된 동작 유발. 에러는 4xx/5xx 로.
디버깅 도구
실제 코드를 받았을 때 의미를 빠르게 확인하려면 HTTP 상태 코드 가 가장 빠르다 — 코드 또는 키워드 검색. URL 자체에 의문이 있다면 URL 파서 로 path/query/fragment 분해. curl 명령으로 재현하고 싶다면 cURL 빌더 가 method/header/body 를 조합해 준다.
요약
- 2xx 성공 / 3xx 리다이렉트 / 4xx 클라이언트 / 5xx 서버.
- 400 = 파싱 실패, 422 = 비즈니스 위반. 401 = 누군지 모름, 403 = 권한 없음.
- API 영구 이전은 308 (method 변경 없음). 301/302 는 브라우저용.
- 429/502/503/504 만 자동 재시도. 나머지 4xx 는 코드/입력 수정 필요.
- HTTP 코드 + 본문
error_code조합이 가장 명확.