HTTP 상태 코드는 서버가 클라이언트에게 “요청이 어떻게 됐는지”를 알려주는 언어입니다. 알 수 없는 422에 머리를 싸매거나, 401403 중 어느 것을 반환해야 할지 고민한 경험이 있다면 이 가이드가 도움이 됩니다. 각 상태 클래스·실제 환경에서 마주치는 코드·클라이언트와 서버 코드에서의 처리 방법을 설명합니다.

상태 코드의 구조

상태 코드는 5개 클래스로 분류되는 3자리 정수입니다:

클래스범위의미
1xx100–199정보 — 요청 수신, 처리 계속 중
2xx200–299성공 — 요청 수신·이해·수락
3xx300–399리다이렉트 — 요청 완료를 위해 추가 액션 필요
4xx400–499클라이언트 오류 — 요청에 문제가 있음
5xx500–599서버 오류 — 유효한 요청에 서버가 실패

첫 번째 자리가 클래스를 결정하고, 나머지 두 자리가 구체적인 상태를 나타냅니다.

1xx: 정보

애플리케이션 레벨 코드에서는 거의 볼 수 없습니다. 서버가 최종 응답 전에 보냅니다.

100 Continue

서버가 요청 헤더를 받았습니다. 클라이언트는 요청 본문을 보내도 됩니다. 대용량 업로드에 Expect: 100-continue와 함께 사용합니다.

101 Switching Protocols

서버가 요청에 따라 다른 프로토콜로 전환 중입니다(HTTP/1.1에서 WebSocket으로 업그레이드 등).

2xx: 성공

200 OK

요청 성공. 응답 본문에 요청한 리소스가 포함됩니다. GET·POST·PUT·PATCH의 기본 성공 코드입니다.

201 Created

새 리소스가 생성되었습니다. 엔티티를 생성하는 POST 요청의 성공에 사용합니다. 응답에는 새 리소스를 가리키는 Location 헤더를 포함해야 합니다.

HTTP/1.1 201 Created
Location: /api/users/456

204 No Content

요청은 성공했지만 응답 본문이 없습니다. DELETE 작업이나 업데이트된 리소스를 반환하지 않는 PUT/PATCH에 사용합니다.

HTTP/1.1 204 No Content

206 Partial Content

서버가 리소스의 일부(범위 요청)를 전달하고 있습니다. 비디오 스트리밍과 재개 가능한 다운로드에서 사용됩니다. Content-Range 헤더가 필요합니다.

3xx: 리다이렉트

301 Moved Permanently

리소스가 새 URL로 영구적으로 이동했습니다. 클라이언트와 검색 엔진은 북마크를 업데이트해야 합니다. SEO 평가가 새 URL로 전달됩니다. 안정적인 API에서 라우트 이름을 변경할 때 사용합니다.

302 Found(임시 리다이렉트)

리소스가 임시로 다른 URL에 있습니다. 클라이언트는 북마크를 업데이트하지 않아야 합니다. 영구적 의도인데 302를 사용하지 마세요. Google은 302를 통해 전체 PageRank를 전달하지 않습니다.

304 Not Modified

캐시된 버전이 아직 유효합니다. ETag / Last-Modified 조건부 요청과 함께 사용됩니다. 서버는 본문을 보내지 않으며 클라이언트는 캐시를 사용합니다. API와 CDN 성능에 중요합니다.

307 Temporary Redirect / 308 Permanent Redirect

302/301과 유사하지만 HTTP 메서드가 유지됩니다. 307로 리다이렉트된 URL로의 POST는 새 URL로의 POST로 유지됩니다. 메서드 유지가 중요한 경우(폼 제출 등) 302/301 대신 사용합니다.

4xx: 클라이언트 오류

400 Bad Request

요청 형식이 잘못되었습니다. 서버가 파싱할 수 없습니다. 문법 오류·유효하지 않은 JSON·필수 필드 누락·타입 불일치에 사용합니다.

HTTP/1.1 400 Bad Request
{ "error": "Invalid JSON body" }

401 Unauthorized

인증이 필요하지만 제공되지 않았거나 자격 증명이 유효하지 않습니다. 응답에는 WWW-Authenticate 헤더를 포함해야 합니다. 이름이 오해의 소지가 있지만, 실질적으로는 “미인증” 상태를 의미합니다.

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="api"

403 Forbidden

서버가 요청을 이해했지만 승인을 거부합니다. 클라이언트는 인증되어 있지만 권한이 없습니다. 사용자가 로그인되어 있지만 필요한 역할이 없는 경우 403을 반환합니다.

401 vs 403: 401 = “당신은 누구입니까?”, 403 = “당신이 누군지 알지만, 이것은 할 수 없습니다”

404 Not Found

리소스가 존재하지 않습니다. 리소스의 존재를 드러내는 것이 보안 문제가 될 때 미인증 사용자로부터 리소스를 숨기기 위해서도 사용합니다(403 대신).

405 Method Not Allowed

HTTP 메서드가 이 엔드포인트에서 지원되지 않습니다. 유효한 메서드를 나열한 Allow 헤더를 반드시 포함하세요.

HTTP/1.1 405 Method Not Allowed
Allow: GET, POST

409 Conflict

요청이 리소스의 현재 상태와 충돌합니다. 대표적 사용 사례: 이미 존재하는 이메일로 사용자 생성, 같은 레코드에 대한 동시 수정.

410 Gone

리소스가 존재했지만 영구적으로 삭제되었습니다. 404와 달리 410은 클라이언트와 검색 엔진에 이 URL을 다시 요청하지 말라고 알립니다.

422 Unprocessable Entity

요청은 올바른 형식이지만 시맨틱 유효성 검사에 실패했습니다. JSON은 올바르게 파싱되지만 비즈니스 규칙을 위반한 경우 422를 사용합니다(시작일보다 앞선 종료일, 음수 수량 등).

429 Too Many Requests

속도 제한 초과. 클라이언트가 언제 재시도할 수 있는지 알려주는 Retry-After 헤더를 포함하세요.

HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0

5xx: 서버 오류

500 Internal Server Error

서버가 예상치 못한 상태를 만났습니다. 처리되지 않은 예외의 기본 코드입니다. 세부 정보는 서버 측에 로그하고, 스택 트레이스는 클라이언트에 노출하지 마세요.

502 Bad Gateway

게이트웨이나 프록시가 업스트림 서버로부터 유효하지 않은 응답을 받았습니다. 로드 밸런서가 애플리케이션 서버에 도달할 수 없을 때 자주 발생합니다.

503 Service Unavailable

서버가 일시적으로 요청을 처리할 수 없습니다(과부하·유지보수·서킷 브레이커 개방). 계획된 다운타임의 경우 Retry-After 헤더를 사용합니다.

504 Gateway Timeout

게이트웨이나 프록시가 업스트림으로부터 적시에 응답을 받지 못했습니다. 흔한 원인: 느린 데이터베이스 쿼리, 다운스트림 API 타임아웃.

올바른 상태 코드 선택

일반적인 API 시나리오의 결정 트리:

시나리오코드
GET이 리소스 반환200
POST가 리소스 생성201
DELETE 성공(본문 없음)204
유효하지 않은 요청 본문 / 문법 오류400
인증 토큰 누락 또는 무효401
유효한 토큰이지만 권한 부족403
리소스를 찾을 수 없음404
이메일 이미 등록됨(중복)409
유효성 검사 실패(시맨틱스)422
처리되지 않은 예외500

코드에서 상태 코드 처리

JavaScript(fetch)

async function apiRequest(url, options) {
  const res = await fetch(url, options);

  if (res.ok) {
    return res.json();          // 200–299
  }

  if (res.status === 401) {
    redirectToLogin();
    return;
  }

  if (res.status === 429) {
    const retryAfter = res.headers.get('Retry-After');
    throw new Error(`Rate limited. Retry after ${retryAfter}s`);
  }

  const error = await res.json().catch(() => ({}));
  throw new Error(error.message ?? `HTTP ${res.status}`);
}

Python(httpx / requests)

import httpx

with httpx.Client() as client:
    r = client.get("https://api.example.com/resource")

    if r.status_code == 200:
        data = r.json()
    elif r.status_code == 404:
        raise ValueError("Resource not found")
    elif r.status_code == 429:
        retry_after = r.headers.get("Retry-After", "60")
        raise Exception(f"Rate limited, retry in {retry_after}s")
    else:
        r.raise_for_status()

빠른 참조

ZeroTool에서 HTTP 상태 코드를 즉시 검색하기 →

코드 번호나 키워드로 검색하고, 클래스별(1xx~5xx)로 탐색하여 브라우저를 벗어나지 않고 설명과 일반적인 사용 사례를 확인할 수 있습니다. API 응답을 디버깅하는 중에 별도 탭에서 MDN을 여는 것보다 빠릅니다.