HTTP 상태 코드는 RFC 9110 (HTTP Semantics)에 정의된 3자리 정수 응답 코드로, 서버가 클라이언트의 요청을 어떻게 처리했는지를 나타낸다.
상태 코드의 첫 번째 자릿수는 응답의 클래스(class) 를 구분하며, 총 5개의 클래스로 나뉜다.
| Class | 범위 | 의미 |
|---|---|---|
| 1xx | 100–199 | Informational — 요청 수신, 처리 계속 |
| 2xx | 200–299 | Successful — 요청 정상 처리 |
| 3xx | 300–399 | Redirection — 추가 동작 필요 |
| 4xx | 400–499 | Client Error — 클라이언트 측 오류 |
| 5xx | 500–599 | Server Error — 서버 측 오류 |
1. 1xx — Informational
| Code | Reason Phrase | 설명 |
|---|---|---|
| 100 | Continue | 클라이언트가 Expect: 100-continue 헤더와 함께 요청을 보낸 경우, 서버가 본문 전송을 계속해도 좋다고 알림 |
| 101 | Switching Protocols | Upgrade 헤더를 통한 프로토콜 전환 수락 (e.g. HTTP → WebSocket) |
| 102 | Processing | 요청 처리 중임을 알림 (WebDAV, RFC 2518) |
💡 포인트
- 100 Continue는 대용량 파일 업로드 시 불필요한 본문 전송을 방지하는 데 사용된다. 서버가 헤더만으로 요청을 거부할 수 있어 네트워크 비용을 절감할 수 있다.
- 101은 WebSocket 핸드셰이크에서 확인할 수 있다.
2. 2xx — Successful
| Code | Reason Phrase | 설명 | 주요 사용 메서드 |
|---|---|---|---|
| 200 | OK | 요청 성공. 응답 본문에 결과 포함 | GET, PUT, PATCH |
| 201 | Created | 리소스 생성 성공.Location 헤더에 새 리소스 URI 포함 권장 |
POST |
| 202 | Accepted | 요청 접수 완료, 비동기 처리 시작 | POST (비동기 작업) |
| 204 | No Content | 요청 성공, 응답 본문 없음 | DELETE, PUT |
💡 포인트
200 vs 201 vs 204 선택 기준
POST /api/users → 201 Created + Location: /api/users/42
GET /api/users/42 → 200 OK + Body
PUT /api/users/42 → 200 OK (본문 반환 시) 또는 204 No Content
DELETE /api/users/42 → 204 No Content
202 Accepted 활용 패턴
비동기 작업(이메일 발송, 대용량 CSV Export 등)에서는 202를 반환하고, 작업 상태를 조회할 수 있는 Polling Endpoint를 함께 제공하는 것이 일반적이다.
POST /api/reports/export → 202 Accepted
Location: /api/jobs/abc-123
GET /api/jobs/abc-123 → 200 OK
{ "status": "processing", "progress": 65 }
3. 3xx — Redirection
| Code | Reason Phrase | 영구/임시 | 메서드 변경 | 설명 |
|---|---|---|---|---|
| 301 | Moved Permanently | 영구 | ⚠️ 가능 | 리소스가 영구적으로 새 URI로 이동. 일부 클라이언트가 POST → GET으로 변경할 수 있음 |
| 302 | Found | 임시 | ⚠️ 가능 | 임시 리다이렉트. 마찬가지로 메서드 변경 가능성 있음 |
| 303 | See Other | — | ✅ GET 강제 | 리다이렉트 시 반드시 GET으로 변경 |
| 304 | Not Modified | — | — | 조건부 요청에서 캐시 유효함을 알림 |
| 307 | Temporary Redirect | 임시 | ❌ 유지 | 임시 리다이렉트. 메서드 변경 금지 |
| 308 | Permanent Redirect | 영구 | ❌ 유지 | 영구 리다이렉트. 메서드 변경 금지 |
💡 포인트
리다이렉트 코드 선택 플로우
영구 이동인가?
├── Yes → 메서드를 유지해야 하는가?
│ ├── Yes → 308 Permanent Redirect
│ └── No → 301 Moved Permanently
└── No → 메서드를 유지해야 하는가?
├── Yes → 307 Temporary Redirect
└── No → 302 Found (또는 303 See Other)
⚠️ 주의: 301/302는 역사적 이유로 일부 클라이언트가 POST를 GET으로 바꾸는 동작이 있다.
메서드 보존이 중요한 API에서는 307/308을 사용해야 한다.
304 Not Modified와 캐시 전략
Client → If-None-Match: "etag-value"
Server → 304 Not Modified (본문 없이 응답)
ETag / Last-Modified 기반의 조건부 요청은 API 응답 트래픽을 대폭 줄일 수 있다. CDN 환경에서는 필수적으로 고려해야 할 전략이다.
4. 4xx — Client Error
4-1. 요청 형식 오류
| Code | Reason Phrase | 설명 |
|---|---|---|
| 400 | Bad Request | 요청 구문 오류, 유효하지 않은 파라미터, 잘못된 JSON 형식 등 |
| 405 | Method Not Allowed | 해당 리소스에서 지원하지 않는 HTTP 메서드. Allow 헤더에 허용 메서드 목록 포함 필수 |
| 406 | Not Acceptable | Accept 헤더에 서버가 제공 불가한 미디어 타입만 명시된 경우 |
| 413 | Content Too Large | 요청 본문이 서버 허용 크기 초과 |
| 414 | URI Too Long | URI 길이 초과 |
| 415 | Unsupported Media Type | Content-Type이 서버가 처리할 수 없는 미디어 타입 |
| 422 | Unprocessable Content | 구문은 유효하나 비즈니스 로직상 처리 불가 (e.g. 이미 존재하는 이메일로 가입 시도) |
4-2. 인증 / 인가 오류
| Code | Reason Phrase | 설명 |
|---|---|---|
| 401 | Unauthorized | 인증(Authentication) 실패. 유효한 자격 증명이 없음 |
| 403 | Forbidden | 인가(Authorization) 실패. 인증은 완료되었으나 해당 리소스에 대한 권한 없음 |
⚠️ 401 vs 403: 네이밍이 혼동을 주지만, 401은 "너 누구야(인증)", 403은 "너인 건 알지만 안 돼(인가)"이다.
# 인증 실패
GET /api/admin/users
Authorization: (없음)
→ 401 Unauthorized
# 인가 실패
GET /api/admin/users
Authorization: Bearer <일반_유저_토큰>
→ 403 Forbidden
4-3. 리소스 상태 오류
| Code | Reason Phrase | 설명 |
|---|---|---|
| 404 | Not Found | 리소스가 존재하지 않음. 보안상 403 대신 404를 반환하기도 함 |
| 409 | Conflict | 리소스의 현재 상태와 충돌 (e.g. 동시 수정, 중복 생성) |
| 410 | Gone | 리소스가 영구적으로 삭제됨. 404와 달리 의도적 삭제를 명시 |
4-4. 기타
| Code | Reason Phrase | 설명 |
|---|---|---|
| 408 | Request Timeout | 서버가 대기 시간 내에 완전한 요청을 수신하지 못함 |
| 429 | Too Many Requests | Rate Limit 초과. Retry-After 헤더에 재시도 가능 시점 포함 권장 |
💡 포인트
400 vs 422 선택 기준
| 상황 | 적절한 코드 |
|---|---|
| JSON 파싱 실패, 필수 필드 누락 | 400 |
| 구문은 유효하나 비즈니스 규칙 위반 (e.g. 비밀번호 정책 미충족) | 422 |
429 응답 시 에러 바디 설계 예시
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "요청 한도를 초과했습니다. 60초 후 다시 시도해 주세요.",
"retryAfter": 60
}
}
보안 고려사항: 404 vs 403
리소스의 존재 여부 자체를 노출하고 싶지 않은 경우(e.g. 다른 사용자의 비공개 게시글), 403 대신 404를 반환하여 리소스 존재 자체를 숨기는 것이 일반적인 보안 패턴이다.
5. 5xx — Server Error
| Code | Reason Phrase | 설명 |
|---|---|---|
| 500 | Internal Server Error | 예상치 못한 서버 에러. catch되지 않은 예외 등 |
| 501 | Not Implemented | 서버가 해당 요청 메서드를 인식하지 못하거나 구현하지 않음 |
| 502 | Bad Gateway | 게이트웨이/프록시가 업스트림 서버로부터 잘못된 응답 수신 |
| 503 | Service Unavailable | 일시적 과부하 또는 점검 중. Retry-After 헤더 포함 권장 |
| 504 | Gateway Timeout | 게이트웨이/프록시가 업스트림 서버로부터 응답을 받지 못함 (타임아웃) |
💡 포인트
502 vs 504 트러블슈팅
[Client] → [Nginx (Reverse Proxy)] → [Application Server]
502: Nginx가 Application Server로부터 유효하지 않은 응답을 받음
→ App 크래시, 잘못된 응답 형식 등
504: Nginx가 Application Server의 응답을 시간 내에 받지 못함
→ 긴 처리 시간, App 행(hang), 네트워크 이슈 등
500 에러 응답 설계 원칙
프로덕션 환경에서 500 에러 발생 시, 내부 구현 세부사항(스택 트레이스, DB 쿼리 등)을 절대 클라이언트에 노출하지 않아야 한다.
// ❌ Bad — 내부 정보 노출
{
"error": "NullPointerException at UserService.java:142",
"query": "SELECT * FROM users WHERE id = 1"
}
// ✅ Good — 트래킹 ID만 제공
{
"error": {
"code": "INTERNAL_ERROR",
"message": "서버 내부 오류가 발생했습니다.",
"traceId": "abc-123-def-456"
}
}
6. API 에러 응답 표준화 — RFC 9457 (Problem Details)
REST API에서 에러 응답 형식이 제각각이면 클라이언트 측 에러 핸들링이 복잡해진다. RFC 9457은 이를 위한 표준 에러 응답 형식을 정의한다.
{
"type": "https://api.example.com/errors/insufficient-balance",
"title": "잔액이 부족합니다.",
"status": 422,
"detail": "현재 잔액은 1,000원이며, 요청 금액은 5,000원입니다.",
"instance": "/api/transactions/abc-123"
}
| 필드 | 설명 |
|---|---|
type |
에러 유형을 식별하는 URI (문서 링크로 활용 가능) |
title |
사람이 읽을 수 있는 에러 요약 |
status |
HTTP 상태 코드 |
detail |
에러에 대한 구체적 설명 |
instance |
에러가 발생한 구체적 리소스 경로 |
7. 정리 — 상태 코드 선택 플로차트
요청 처리 성공?
├── Yes
│ ├── 리소스 생성? → 201 Created
│ ├── 비동기 처리? → 202 Accepted
│ ├── 응답 본문 있음? → 200 OK
│ └── 응답 본문 없음? → 204 No Content
│
└── No
├── 클라이언트 잘못?
│ ├── 인증 실패? → 401 Unauthorized
│ ├── 인가 실패? → 403 Forbidden
│ ├── 리소스 없음? → 404 Not Found
│ ├── 구문 오류? → 400 Bad Request
│ ├── 비즈니스 규칙 위반? → 422 Unprocessable Content
│ ├── 리소스 충돌? → 409 Conflict
│ └── 요청 제한 초과? → 429 Too Many Requests
│
└── 서버 잘못?
├── 알 수 없는 오류? → 500 Internal Server Error
├── 기능 미구현? → 501 Not Implemented
├── 업스트림 잘못된 응답? → 502 Bad Gateway
├── 서버 과부하/점검? → 503 Service Unavailable
└── 업스트림 타임아웃? → 504 Gateway Timeout
References
- RFC 9110 — HTTP Semantics
- RFC 9457 — Problem Details for HTTP APIs
- MDN Web Docs — HTTP response status codes
- IANA HTTP Status Code Registry
'💡 Tech Note' 카테고리의 다른 글
| 소프트웨어 개발 보안 핵심 가이드 (0) | 2026.02.27 |
|---|---|
| [서버 보안] API 키 관리 전략 및 환경변수 설정 (0) | 2026.02.27 |
| [Git] 실무 핵심 명령어 요약 및 자동 업로드 스크립트(.sh) 공유 (0) | 2026.02.19 |
| [DB/SQL] 자주 쓰는 SQL 명령어 (0) | 2026.02.19 |