← 전체 글로 돌아가기

웹 개발

HTTP 301과 302의 차이, 그리고 잘못 쓰면 생기는 일

301은 브라우저와 검색엔진이 캐시한다. 임시 상황에 301을 썼다가 되돌리려면 생각보다 오래 걸린다.

리다이렉트 상태 코드는 자주 쓰는 것 같지만 실제로 어떻게 동작하는지 정확히 알고 쓰는 경우는 드물다. 301이랑 302를 아무거나 가져다 쓰다가 나중에 고치기 어려운 상황이 되기도 한다.

두 코드의 차이

301 Moved Permanently — 이 URL은 영구적으로 저 URL로 이동했다는 뜻이다. 브라우저는 이 응답을 캐시하고, 이후 같은 URL로 요청이 들어오면 서버에 묻지 않고 바로 새 URL로 이동한다. 검색엔진도 기존 URL의 링크 신호를 새 URL로 넘긴다.

302 Found — 일시적으로 저 URL로 보내달라는 뜻이다. 브라우저가 기본적으로 캐시하지 않고, 다음에 같은 URL에 접근하면 다시 서버에 확인한다.

# curl로 응답 헤더 확인
curl -I https://example.com/old-path

# HTTP/1.1 301 Moved Permanently
# Location: https://example.com/new-path
# Cache-Control: max-age=31536000

301을 잘못 쓰는 흔한 상황

가장 많이 실수하는 경우는 A/B 테스트나 유지보수 페이지 같은 임시 상황에 301을 쓰는 것이다. 사용자 트래픽을 분기하려고 //new로 301을 응답했다가 다시 /로 돌리려고 해도, 브라우저가 301을 캐시해버렸다면 사용자는 계속 /new로 간다. 캐시를 지우려면 사용자가 직접 브라우저 캐시를 비워야 한다. 검색엔진 크롤러가 캐시한 경우도 몇 주씩 걸릴 수 있다.

임시 상황이라면 302를 써야 한다.

POST 요청과 메서드 보존

301/302는 POST 요청을 리다이렉트할 때 원래 명세와 실제 브라우저 동작이 다르다. 명세상으로는 POST → 301이면 POST로 새 URL을 요청해야 하지만, 실제 브라우저는 대부분 GET으로 바꿔서 보낸다. 이 때문에 폼 제출 후 리다이렉트가 의도대로 안 되는 경우가 있다.

HTTP 표준은 이 문제를 해결하기 위해 308(Permanent Redirect)과 307(Temporary Redirect)을 추가했다. 308/307은 원래 메서드를 유지한다.

코드영구여부메서드 유지
301영구미보장 (GET으로 변경될 수 있음)
302임시미보장
307임시보장
308영구보장

SEO와 리다이렉트

도메인 변경이나 URL 구조 개편처럼 진짜 영구적인 이전이라면 301이 맞다. 링크 신호가 새 URL로 옮겨가길 원할 때 쓴다. 로그인 후 원래 페이지로 돌려보내는 것, 지역/언어 감지로 분기하는 것 등은 302다.

nginx에서 리다이렉트 설정 예시:

# 영구 이전
return 301 https://new.example.com$request_uri;

# 임시 이전 (기본값이 302)
return 302 /maintenance;