HTTP 상태 코드 정리
HTTP 상태 코드는 서버가 클라이언트의 요청을 처리한 결과를 세 자리 숫자로 알려 주는 값이다.
브라우저나 앱은 이 코드를 보고 페이지를 표시할지, 다른 주소로 이동할지, 재로그인을 요청할지, 오류 화면을 보여 줄지 판단합니다.
개발자는 모든 번호를 외우기보다 첫 번째 숫자가 나타내는 범주부터 이해하는 편이 좋습니다.
HTTP 상태 코드의 구조
상태 코드는 100부터 599까지 다섯 범주로 나뉩니다.
1xx: 요청 처리가 계속되고 있음을 알리는 정보 응답2xx: 요청을 성공적으로 처리한 응답3xx: 다른 위치로 이동하거나 캐시를 사용해야 하는 응답4xx: 요청 형식, 인증, 권한, 대상 등 클라이언트 측에서 확인할 문제가 있는 응답5xx: 서버가 올바른 요청을 처리하지 못한 응답
첫 번째 숫자는 큰 방향을 알려 주고, 뒤의 두 숫자는 구체적인 상태를 구분합니다.
1xx 정보 응답
1xx 응답은 요청이 끝나지 않았고 중간 단계를 지나고 있음을 알립니다.
일반적인 웹 개발에서는 2xx부터 5xx보다 직접 다룰 일이 적습니다.
100 Continue은 클라이언트가 요청 본문을 계속 보내도 된다는 중간 응답입니다.
101 Switching Protocols는 서버가 프로토콜 변경 요청을 수락했을 때 사용합니다.
2xx 성공 응답
2xx는 요청을 성공적으로 받고 처리했다는 범주입니다.
다만 성공했다는 결과가 항상 200 OK를 의미하지는 않습니다.
200 OK
200 OK는 요청이 성공했고 응답 본문에 결과를 담아 보낼 때 가장 널리 사용합니다.
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 42,
"title": "HTTP 상태 코드 정리"
}GET으로 데이터를 조회하거나 기존 데이터를 수정한 결과를 보낼 때 자주 볼 수 있습니다.
201 Created
201 Created는 요청 결과로 새 리소스가 생성되었음을 알립니다.
게시글, 회원, 주문을 새로 만드는 POST 요청에 잘 어울립니다.
생성된 리소스의 URL을 Location 헤더로 알려 줄 수 있습니다.
HTTP/1.1 201 Created
Location: /posts/42
Content-Type: application/json
{
"id": 42
}204 No Content
204 No Content는 요청은 성공했지만 보낼 응답 본문이 없을 때 사용합니다.
삭제가 성공했고 추가 데이터가 필요하지 않은 경우가 대표적입니다.
204 응답에는 본문을 넣지 않아야 합니다.
200, 201, 204의 차이
세 코드는 모두 성공을 나타내지만 결과의 성격이 다릅니다.
200 OK: 성공 결과를 응답 본문으로 보내는 경우201 Created: 새 리소스를 생성한 경우204 No Content: 성공했지만 보낼 본문이 없는 경우
성공이라는 이유만으로 모두 200을 반환하면 클라이언트가 결과의 의미를 추가로 해석해야 합니다.
3xx 리다이렉션과 캐시
3xx는 요청을 완료하기 위해 다른 위치로 이동하거나 캐시된 결과를 사용해야 함을 나타냅니다.
301 Moved Permanently
301 Moved Permanently는 리소스의 URL이 영구적으로 바뀌었음을 알립니다.
새 URL은 보통 Location 헤더에 담깁니다.
검색 엔진과 브라우저는 앞으로 새 주소를 사용해야 한다고 해석할 수 있습니다.
302 Found
302 Found는 리소스가 일시적으로 다른 URL에 있음을 알립니다.
원래 URL을 계속 사용할 예정이라면 301보다 302가 어울립니다.
304 Not Modified
304 Not Modified는 리소스가 이전 요청 이후 바뀌지 않았으므로 이미 저장한 캐시를 사용해도 된다는 뜻입니다.
서버가 같은 본문을 다시 보내지 않아도 되므로 네트워크 사용량을 줄일 수 있습니다.
304는 보통의 성공 본문 대신 캐시 재사용을 지시하는 응답입니다.
4xx 클라이언트 오류
4xx는 서버가 요청을 처리하기 어려운 이유가 요청 측에 있음을 나타냅니다.
클라이언트는 같은 요청을 반복하기보다 파라미터, 인증 정보, 권한, URL을 먼저 확인해야 합니다.
400 Bad Request
400 Bad Request는 요청 문법이 틀렸거나 필수 값이 없는 등 요청을 이해하기 어려울 경우에 사용합니다.
깨진 JSON, 잘못된 파라미터 형식, 필수 헤더 누락이 예시입니다.
401 Unauthorized
401 Unauthorized는 현재 요청을 보낸 사용자를 올바르게 인증하지 못했다는 뜻입니다.
로그인하지 않았거나 토큰이 없고, 만료되었거나, 잘못된 경우에 주로 반환합니다.
403 Forbidden
403 Forbidden은 서버가 요청을 이해했지만 사용자에게 해당 작업을 수행할 권한이 없음을 나타냅니다.
일반 회원이 관리자 페이지에 접근하려는 경우가 예시입니다.
404 Not Found
404 Not Found는 요청한 리소스를 찾을 수 없음을 나타냅니다.
URL이 틀렸거나 대상이 삭제된 경우에 주로 발생합니다.
리소스의 존재 자체를 노출하지 않기 위해 권한이 없는 사용자에게 404를 반환하는 설계도 있습니다.
기타 4xx 코드
405 Method Not Allowed는 URL은 있지만 해당 HTTP 메서드를 허용하지 않는 경우입니다.
409 Conflict는 현재 리소스 상태와 요청이 충돌하는 경우에 사용합니다.
이미 존재하는 아이디로 회원을 생성하거나 낮은 버전의 데이터로 수정하려는 경우가 예시입니다.
422 Unprocessable Content는 요청 형식은 올바르지만 내용의 의미를 검증하는 단계에서 처리할 수 없는 경우에 사용합니다.
이메일 형식, 비밀번호 길이, 수량 범위 같은 검증 실패를 표현할 때 쓸 수 있습니다.
429 Too Many Requests는 짧은 시간에 너무 많은 요청을 보내 제한에 걸렸음을 나타냅니다.
서버는 Retry-After 헤더로 다시 시도할 시점을 알려 줄 수 있습니다.
401과 403의 차이
401과 403은 모두 접근 실패와 관련되지만 확인할 지점이 다릅니다.
401 Unauthorized: 누구인지 올바르게 인증되지 않음403 Forbidden: 누구인지 알지만 해당 작업을 할 권한이 없음
로그인이 필요하면 401, 로그인한 사용자의 역할이 부족하면 403으로 생각하면 구분하기 쉽습니다.
5xx 서버 오류
5xx는 요청 자체보다 서버의 처리 과정에서 문제가 발생했음을 나타냅니다.
클라이언트가 요청을 수정해도 바로 해결되지 않을 수 있습니다.
500 Internal Server Error
500 Internal Server Error는 서버가 예상하지 못한 오류로 요청을 처리하지 못했을 때 사용하는 일반적인 서버 오류 코드입니다.
처리하지 않은 예외, 데이터베이스 오류, 설정 누락 등 원인은 다양할 수 있습니다.
502 Bad Gateway
502 Bad Gateway는 게이트웨이나 프록시 서버가 뒷단 서버로부터 올바른 응답을 받지 못했을 때 발생합니다.
역방향 프록시, CDN, 로드 밸런서 뒤에 있는 원본 서버의 응답에 문제가 있을 때 볼 수 있습니다.
503 Service Unavailable
503 Service Unavailable은 서버가 현재 요청을 처리할 수 없지만 나중에는 복구될 수 있음을 나타냅니다.
서버 점검, 일시적인 과부하, 자원 부족이 대표적인 원인입니다.
복구 예정 시간을 알 수 있다면 Retry-After 헤더를 함께 보낼 수 있습니다.
504 Gateway Timeout
504 Gateway Timeout은 게이트웨이나 프록시 서버가 뒷단 서버의 응답을 제한 시간 안에 받지 못했을 때 발생합니다.
502가 잘못된 응답을 받은 경우라면 504는 응답을 기다리다가 시간이 끝난 경우에 가깝습니다.
404와 500의 차이
404는 요청한 대상을 찾을 수 없다는 클라이언트 오류입니다.
500은 서버 내부에서 예상하지 못한 문제로 처리에 실패했다는 서버 오류입니다.
존재하지 않는 게시글을 조회했다면 404가 자연스럽습니다.
게시글은 있지만 조회 중 데이터베이스 예외가 발생했다면 500이 자연스럽습니다.
모든 결과를 200으로 반환하는 문제
다음처럼 실패한 요청도 200 OK로 반환하는 API를 볼 수 있습니다.
HTTP/1.1 200 OK
Content-Type: application/json
{
"success": false,
"error": "post not found"
}응답 본문을 읽으면 실패이지만 HTTP 상태 코드만 보면 성공입니다.
이러한 설계는 브라우저, CDN, 모니터링 도구, API 클라이언트가 요청 결과를 정확히 판단하기 어렵게 만듭니다.
HTTP 상태 코드는 요청의 결과를 나타내고, JSON 본문은 결과의 상세 정보를 제공하는 역할로 나누는 편이 좋습니다.
fetch의 상태 코드 처리
브라우저의 fetch()는 404나 500 응답을 받았다는 이유만으로 Promise를 reject하지 않습니다.
서버와 HTTP 응답을 주고받는 데 성공했기 때문에 Response 객체로 resolve됩니다.
따라서 response.ok나 response.status를 직접 확인해야 합니다.
const response = await fetch('/api/posts/42')
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`)
}
const post = await response.json()response.ok는 상태 코드가 200에서 299 사이일 때 true입니다.
response.status를 사용하면 401에서는 로그인 화면으로 이동하고, 404에서는 대상이 없다는 UI를 보여 주는 식으로 분기할 수 있습니다.
재시도 기준
오류가 발생했다고 모든 요청을 즉시 다시 보내면 서버 부하를 더 키울 수 있습니다.
400, 401, 403, 404, 422 같은 대부분의 4xx 응답은 요청을 그대로 반복해도 해결되지 않습니다.
요청 내용, 인증 정보, 권한을 먼저 바꿔야 합니다.
429, 502, 503, 504처럼 일시적일 수 있는 오류는 시간 간격을 늘려 재시도할 수 있습니다.
서버가 Retry-After를 보냈다면 그 값을 우선해야 합니다.
재시도 간격은 1초, 2초, 4초처럼 점진적으로 늘리는 방식을 사용할 수 있습니다.
주문 생성처럼 반복 시 같은 작업이 중복 실행될 수 있는 요청은 멱등성을 보장하는 장치가 필요합니다.
상태 코드를 고르는 순서
먼저 요청이 성공했는지 확인합니다.
성공했다면 기존 결과 반환은 200, 새 리소스 생성은 201, 본문 없는 성공은 204를 검토합니다.
요청 측 문제라면 인증 여부, 권한, 대상 존재, 요청 검증 순으로 4xx 코드를 고릅니다.
서버 측 문제라면 자체 처리 오류인지, 뒷단 서버의 잘못된 응답인지, 일시적 사용 불가인지, 응답 시간 초과인지로 5xx 코드를 나눕니다.
상태 코드는 오류 메시지를 대신하는 값이 아닙니다.
클라이언트가 해결 방법을 판단할 수 있도록 적절한 상태 코드와 읽을 수 있는 오류 본문을 함께 제공하는 편이 좋습니다.
HTTP 요청 방식은 HTTP GET과 POST 차이에서, 브라우저 요청 도구는 fetch와 axios 차이에서 이어서 볼 수 있습니다.
다른 출처의 API를 호출할 때 발생하는 문제는 CORS 에러와 해결 기준에서, URL에 값을 담는 방법은 URL path parameter와 query string 차이에서 정리했습니다.
참고 자료
















