301과 302 리다이렉트 차이, 되돌리기 어려운 쪽은

주소를 옮기면서 리다이렉트를 걸어야 하는데 301과 302 중 어느 쪽인지 막히신 분을 위한 글입니다. 결론부터 말하면 301은 주소가 영구히 바뀌었다는 선언이고, 302는 지금만 잠깐 다른 곳에 있다는 선언입니다.
이 차이는 말장난이 아니라 두 곳에서 실제로 다르게 동작합니다. 하나는 검색엔진이 어느 주소를 정본으로 삼느냐, 다른 하나는 브라우저가 그 응답을 캐시에 저장하느냐입니다.
요약 ① 301은 영구 이전, 302는 임시 이전입니다. 302를 받은 클라이언트는 앞으로도 원래 주소를 계속 씁니다. ② 검색엔진은 영구 리다이렉트만 새 주소를 정본으로 삼는 신호로 쓰고, 임시 리다이렉트에서는 원래 주소를 검색 결과에 남깁니다. ③ 301은 만료 시간이 없어도 캐시가 스스로 기간을 정해 저장할 수 있는 상태 코드라, 잘못 걸면 방문자 브라우저에서 되돌리기 어렵습니다.
두 코드가 실제로 선언하는 것
HTTP 규격(RFC 9110)의 정의는 짧습니다.
- 301 Moved Permanently — 대상 리소스에 새 영구 URI가 배정되었으며, 앞으로의 참조는 새 URI를 쓰라는 뜻입니다.
- 302 Found — 대상 리소스가 일시적으로 다른 URI에 있다는 뜻입니다. 규격은 여기에 한 줄을 덧붙입니다. 리다이렉트가 나중에 바뀔 수 있으므로 클라이언트는 앞으로의 요청에도 원래 주소를 계속 써야 한다는 것입니다.
즉 302는 "이번만 저리로 가라"이고, 301은 "앞으로 그 주소는 잊어라"입니다. 같은 화면으로 넘어가는 것처럼 보여도 클라이언트에게 남기는 지시가 정반대입니다.
검색엔진에서는 정본 신호가 갈립니다
구글 검색 문서는 두 경우를 나눠 설명합니다. 영구 리다이렉트는 색인 과정에서 리다이렉트 대상이 정본(canonical)이라는 신호로 쓰이고, 검색 결과에는 새 주소가 표시됩니다.
임시 리다이렉트는 그 신호로 쓰이지 않습니다. 크롤러는 리다이렉트를 따라가지만, 검색 결과에는 원래 페이지가 그대로 남습니다.
그래서 선택 기준은 취향이 아니라 의도입니다. 검색 결과에 남기고 싶은 주소가 새 주소면 301, 원래 주소면 302입니다.
점검 중이라 잠시 안내 페이지로 보내거나, 기간 한정 페이지로 임시 우회시키는 경우가 302의 자리입니다.
301이 되돌리기 어려운 이유 — 브라우저 캐시
여기가 실무에서 사고가 나는 지점입니다.
HTTP 캐시 규격(RFC 9111)은 응답에 명시적인 만료 시간이 없을 때 캐시가 스스로 유효기간을 추정하는 것을 허용합니다. 이를 휴리스틱 캐싱이라고 합니다.
다만 아무 응답에나 쓸 수 있는 것은 아닙니다. 규격이 휴리스틱 캐시가 가능하다고 명시한 상태 코드에만 적용됩니다. 301은 그 목록에 있고, 302는 없습니다.
- 301:
Cache-Control이 없어도 캐시가 알아서 기간을 정해 저장할 수 있습니다. - 302: 명시적인 캐시 지시가 없으면 휴리스틱으로 저장되지 않습니다.
저장된 301은 서버를 거치지 않습니다
방문자 브라우저가 301을 한 번 저장하면, 다음부터는 서버에 묻지 않고 곧장 새 주소로 갑니다. 서버에서 규칙을 지워도 그 방문자에게는 리다이렉트가 계속 걸린 것처럼 보입니다.
서버 로그에는 그 요청이 아예 남지 않습니다. "고쳤는데 왜 계속 넘어가지" 상태가 되는 이유입니다.
게다가 남의 브라우저에 저장된 캐시는 우리가 지울 수 없습니다. 서버 쪽만 고치고 기다리는 것 외에 할 수 있는 일이 거의 없습니다.
그래서 하는 일
- 확정되지 않은 이전은 302로 시작합니다. 되돌릴 일이 없다고 판단된 뒤에 301로 올립니다.
- 301을 쓸 때도 응답에
Cache-Control로 만료 시간을 명시하면, 캐시가 임의로 기간을 추정하지 않습니다. 설정 방법은 웹서버마다 다릅니다. - 내 브라우저에서 확인할 때는 개인정보 보호 창(시크릿 창)을 쓰거나 브라우저 캐시를 지웁니다. 저장된 리다이렉트가 없는 상태에서 다시 요청하게 됩니다.
도구 없이 확인하고 설정하는 방법
지금 어떤 코드가 나가는지 확인하기
터미널에서 헤더만 받아 보면 됩니다.
curl -I https://example.com/old-page
첫 줄에 HTTP/1.1 301 Moved Permanently 또는 HTTP/1.1 302 Found 가 나오고, 아래쪽 Location: 에 목적지 주소가 찍힙니다.
리다이렉트가 여러 단계로 이어져 있는지 보려면 -IL 을 씁니다. 따라가며 각 단계의 상태 코드를 순서대로 보여 줍니다.
curl 은 브라우저 캐시를 거치지 않습니다. 그래서 서버가 지금 실제로 보내는 응답을 그대로 보여 줍니다. curl 결과는 302인데 브라우저만 계속 넘어간다면, 원인은 서버가 아니라 캐시입니다.
브라우저 개발자 도구(대개 F12)의 네트워크 탭에서도 상태 코드 열로 확인할 수 있습니다. 이때 캐시 사용 중지(Disable cache)를 켜야 서버 응답을 보게 됩니다.
Apache 에서 코드 고르기
.htaccess 로 리다이렉트를 거는 방법은 두 가지입니다. 둘 다 기본값이 302라는 점이 중요합니다.
첫째는 mod_alias 의 Redirect 입니다. 상태를 앞에 적으며, 숫자 대신 이름도 쓸 수 있습니다.
Redirect 301 /old-page https://example.com/new-page
Redirect permanent /old-page https://example.com/new-page
Redirect temp /old-page https://example.com/new-page
permanent 가 301, temp 가 302, seeother 가 303입니다. Apache 문서 기준으로 상태를 생략하면 302가 됩니다. 영구 이전인데 임시로 나가는 사고가 여기서 자주 생깁니다.
둘째는 mod_rewrite 의 R 플래그입니다.
RewriteRule ^old-page$ https://example.com/new-page [R=301,L]
R 만 쓰고 코드를 생략하면 역시 302가 기본값입니다. 301을 원하면 R=301 처럼 반드시 적어야 합니다.
문제는 HTTPS 강제나 www 통일 규칙까지 함께 쓰기 시작하면 조건문 순서와 플래그가 금세 길어진다는 점입니다.
저희 도구로 확인하기. 기본 규칙을 체크박스로 고르고 www 처리를 선택한 뒤 이전 경로와 새 주소를 넣으면, 완성된 .htaccess 본문이 바로 나옵니다.
설치·회원가입 없이 브라우저 안에서 규칙이 조립되며, 입력한 경로는 서버로 전송되지 않습니다.

이 생성기가 만드는 리다이렉트는 전부 301입니다. 임시로 걸어야 한다면 나온 결과에서 Redirect 301 을 Redirect 302 로, [L,R=301] 을 [L,R=302] 로 바꾸면 됩니다.
301·302·307·308 네 코드의 자리
301과 302에는 한 가지 함정이 더 있습니다. RFC 9110은 두 코드 모두 역사적인 이유로 사용자 에이전트가 POST 요청을 GET 으로 바꿔 보낼 수 있다고 적어 두었습니다.
메서드를 반드시 유지해야 한다면 307(임시)과 308(영구)을 씁니다. 307은 규격이 메서드를 바꾸지 말라고 못 박은 코드이고, 308은 그 영구 버전입니다.
| 코드 | 의미 | 요청 메서드 | 만료 시간 없이 캐시 저장 |
|---|---|---|---|
| 301 | 영구 이전 | POST가 GET으로 바뀔 수 있음 | 가능 |
| 302 | 임시 이전 | POST가 GET으로 바뀔 수 있음 | 불가 |
| 307 | 임시 이전 | 반드시 유지 | 불가 |
| 308 | 영구 이전 | 반드시 유지 | 가능 |
일반적인 페이지 이전은 GET 요청이라 301·302로 충분합니다. 폼 전송(POST)을 받는 주소를 옮길 때만 307·308을 따져 보면 됩니다.
자주 막히는 지점
| 상황 | 어떻게 되나 |
|---|---|
| A→B→C 체인 | 단계마다 왕복이 늘고, 중간 규칙이 남아 있으면 정리가 어렵습니다 |
| 규칙끼리 되받음 | www 강제와 HTTPS 강제가 서로를 가리키면 순환이 생깁니다 |
| 301을 먼저 걸고 취소 | 서버는 고쳐도 저장된 캐시는 남습니다 |
| 상태 코드 생략 | Apache 두 방식 모두 302로 나갑니다 |
순환 리다이렉트에 대해 규격은 클라이언트가 이를 감지해 개입해야 한다고 정하고 있습니다. 예전 판이 최대 다섯 번을 권고했다는 주석도 남아 있어, 단계가 길어지면 도중에 끊길 수 있습니다.
새 규칙을 추가할 때는 중간 주소가 아니라 최종 주소로 한 번에 보내는 편이 안전합니다. .htaccess 는 한 줄 오타로 사이트 전체가 500 오류를 낼 수 있으므로, 반영 전 백업본을 남겨 두시기 바랍니다.
정리
- 301은 영구, 302는 임시입니다. 302를 받은 클라이언트는 앞으로도 원래 주소를 계속 씁니다.
- 검색엔진은 영구 리다이렉트만 새 주소를 정본으로 삼는 신호로 쓰고, 임시 리다이렉트에서는 원래 주소를 검색 결과에 남깁니다.
- 301은 만료 시간이 없어도 캐시가 저장할 수 있는 상태 코드입니다. 저장된 뒤에는 서버를 거치지 않아 되돌리기 어렵습니다.
- 확정되지 않은 이전은 302로 시작하고, 되돌릴 일이 없어진 뒤 301로 올리면 안전합니다.
- 지금 나가는 코드는
curl -I로 확인합니다. curl은 브라우저 캐시를 거치지 않습니다. - Apache의
Redirect와RewriteRule [R]은 둘 다 생략 시 302입니다. 301은 명시해야 합니다. - POST 메서드를 유지해야 하면 307·308을 씁니다.
설치·회원가입 없이 브라우저 안에서 규칙이 조립되며, 입력한 경로는 서버로 전송되지 않습니다.
자주 묻는 질문
301을 302로 바꿨는데 브라우저가 계속 옛날대로 넘어갑니다
먼저 curl -I 로 서버 응답을 확인해 보시기 바랍니다. curl에서 302가 나온다면 서버는 이미 고쳐진 것이고, 남은 원인은 브라우저에 저장된 301 캐시입니다. 개인정보 보호 창에서 열거나 브라우저 캐시를 지우면 본인 화면에서는 풀립니다. 다만 다른 방문자의 캐시는 지울 수 없어, 만료될 때까지 기다려야 합니다.
SEO 때문에 무조건 301을 쓰는 게 낫나요
새 주소를 검색 결과에 남기려는 상황이면 301이 맞습니다. 하지만 되돌릴 가능성이 있는 이전에 301을 걸면, 취소하려 할 때 방문자 브라우저에 저장된 캐시 때문에 즉시 되돌아가지 않습니다. 구글 문서도 되돌릴 일이 없다고 확신할 때 영구 리다이렉트를 쓰라고 안내합니다.
리다이렉트를 걸었는데 검색 결과 주소가 그대로입니다
색인 반영에는 크롤링이 다시 일어나야 하므로 시간이 걸립니다. 구글은 걸리는 기간을 수치로 제시하지 않습니다. 확인은 추측이 아니라 Search Console의 URL 검사 도구로, 해당 주소를 넣고 구글이 인식한 상태와 선택된 정본 주소를 보는 방식이 정확합니다.
생성기에 입력한 경로가 서버로 전송되나요
아니요. 이 생성기는 체크박스 선택과 입력한 경로를 브라우저 안에서 조립해 결과 텍스트를 만듭니다. 입력값은 서버로 전송되거나 저장되지 않으며, 다운로드 버튼도 브라우저가 만든 파일을 그대로 내려받는 방식입니다.
