307 vs 308 리다이렉트 차이
307·308 리다이렉트는 요청 메서드를 유지합니다. POST를 GET으로 바꾸는 301·302와 어떻게 다른지, 무료 즉시 진단으로 바로 확인.
내 도메인에 이 문제가 있는지 지금 확인
무료, 가입 불필요. 이 가이드가 다루는 항목을 바로 검사하고 조치 방법을 알려드립니다.
Problem
리다이렉트를 보내야 하는데 요청이 POST·PUT·PATCH입니다 — API 호출, 폼 제출, 웹훅 — 그리고 301·302·307·308 중에 골라야 합니다. 잘못 고르면 요청 본문이 사라집니다.
Symptoms
- 옛 URL로 보낸
POST가 새 URL에 본문 없는GET으로 도착하고, 서버가 거부하거나 아무 일도 안 합니다. - HTTP 진단이 메서드를 유지했어야 할 체인에서 301·302 홉을 보여줍니다.
- 브라우저에선 폼 제출이 “되는데”
curl·모바일 앱·서버 간 클라이언트에서 같은 호출이 페이로드를 잃습니다. - 설정한 적 없는 http→https 리다이렉트에 Chrome이 307을 표시합니다(그건 HSTS입니다 — 아래 참고).
What 307 and 308 Actually Change
“어느 리다이렉트 코드냐” 안에는 독립된 질문이 두 개 숨어 있고, 이 둘을 섞는 게 문제의 전부입니다.
질문 1: 영구인가 임시인가? 캐시·SEO 축입니다. 301(RFC 9110 §15.4.2)과 308(RFC 7538, RFC 9110 §15.4.9에 편입)은 영구 — 기본적으로 캐시 가능하고, 검색엔진이 순위 신호를 대상으로 모읍니다. 302(§15.4.3)와 307(§15.4.8)은 임시 — 명시하지 않는 한 캐시되지 않고, “이건 우회로이니 원본을 계속 색인하라”로 취급됩니다.
질문 2: 메서드가 살아남는가? 307·308이 존재하는 이유가 여기 있습니다. 브라우저가 POST에 대해 301이나 302를 따라가면, 거의 모든 클라이언트가 메서드를 GET으로 바꾸고 본문을 버립니다. 이 동작은 20년 묵은 흠입니다: 명세는 늘 메서드를 유지해야 한다고 했지만 초기 브라우저가 바꿔버렸고, 그래서 웹의 상당 부분이 나중에 303 See Other가 공식화한 POST→GET 재작성에 조용히 의존합니다. 307·308은 그 문을 닫습니다. 클라이언트가 새 URL에 대해 똑같은 메서드와 본문을 반복하도록 요구합니다. POST는 POST로 남습니다.
그래서 네 코드는 목록이 아니라 2×2 격자입니다:
- 301 — 영구, 메서드 변경 가능(POST→GET). GET 페이지 이동에 적합.
- 302 — 임시, 메서드 변경 가능. 임시 GET 우회에 적합.
- 307 — 임시, 메서드 유지. POST·PUT·PATCH의 임시 리다이렉트에 사용.
- 308 — 영구, 메서드 유지. API·폼 엔드포인트의 영구 리다이렉트에 사용.
SEO 관점에선 격자가 다시 둘로 접힙니다: Google은 308을 301처럼, 307을 302처럼 취급합니다. 301·302를 넘어설 유일한 이유는 메서드 축입니다.
The HSTS 307 That Isn’t Yours
작성한 적 없는 http:// → https:// 리다이렉트에 정체불명의 307이 떠 있다면, 서버 디버깅을 멈추세요. 도메인이 Strict-Transport-Security 헤더를 한 번 보낸 뒤에는, 브라우저가 이후 모든 http:// 요청을 패킷이 기계를 떠나기 전에 내부적으로 https://로 업그레이드합니다. Chrome DevTools는 이를 Non-Authoritative-Reason: HttpsUpgrade가 붙은 307 내부 리다이렉트로 렌더링합니다. 회선에 나가지 않고, Googlebot은 못 보며(크롤러는 서버의 진짜 301을 봅니다), 설정을 바꿔도 없어지지 않습니다 — 브라우저의 HSTS 저장소에 캐시돼 있습니다. 이게 가장 흔한 “유령 307”이고, 존재하지 않는 규칙을 찾느라 사람들이 몇 시간을 낭비합니다.
Top 3 Causes of the Wrong Code
- 프레임워크 기본값이 POST를 재작성 - 많은 웹 프레임워크·리버스 프록시가
redirect()호출에 301·302를 냅니다. 리다이렉트된 요청이 POST였다면 클라이언트가 본문 없는 GET으로 다시 보냈고, 핸들러는 아무것도 못 봤습니다. 그 특정 리다이렉트를 307·308로 바꾸는 게 해결입니다. - API의 trailing-slash·http→https 정규화 -
/api/thing을 301·302로/api/thing/으로(또는 HTTPS로) 정규화하면, 그 엔드포인트로 가는 모든 비-GET 호출이 깨집니다. API·웹훅 리다이렉트는 307·308이어야 하고, 더 좋게는 쓰기를 아예 리다이렉트하지 마세요. - 영구였어야 할 “임시” 302(또는 그 반대) - 영구 이동을 302로 서빙하면 검색엔진이 옛 URL을 계속 색인하고, 임시 우회를 301·308로 서빙하면 브라우저가 강하게 캐시해 되돌리기가 고통스럽습니다. 영구성을 현실에 맞추세요.
Diagnose with DechoNet
- HTTP 진단으로 URL의 정확한 상태 코드와 전체 홉 체인을 읽으세요. 각 홉이 301·302·307·308 중 무엇인지 확인하고 홉 수를 세세요 — 메서드를 유지했어야 하는데 301·302로 보이는 리다이렉트가 범인입니다.
- 쓰기 경로를 의도적으로 재현하세요: 내비게이션은 보통 GET이라 브라우저는 POST→GET 재작성을 숨깁니다. 실패는
curl -X POST·SDK·서버 간 클라이언트가 리다이렉트를 따라갈 때 드러납니다. 실제 호출자가 쓰는 메서드로 테스트하세요.
Resolution Checklist
- 요청 본문을 실을 수 있는 리다이렉트(POST·PUT·PATCH — API·폼·웹훅)에는 임시면 307, 영구면 308을 사용하세요.
- 단순 GET 페이지 이동은 301(영구)·302(임시)로 충분합니다 — 메서드는 어차피 GET으로 유지됩니다.
- 영구성을 현실에 맞추세요: 영구 이동은 301·308, 진짜 임시는 302·307. 301·308은 브라우저가 강하게 캐시해 되돌리기 어렵다는 점을 기억하세요.
- 쓰기는 아예 리다이렉트하지 않는 걸 선호하세요. 가능하면 메서드 유지 리다이렉트에 기대는 대신 클라이언트가 최종 URL을 직접 겨냥하게 하세요.
- http→https에 설정 안 한 307이 보이면, HSTS 내부 리다이렉트(
Non-Authoritative-Reason: HttpsUpgrade)인지 확인하고 서버 설정 수정을 멈추세요. - HTTP 진단을 다시 돌려 코드와 홉 수가 의도와 맞는지 확인하세요.
When to Escalate
- 리다이렉트가 애플리케이션 위의 CDN·리버스 프록시에서 추가된다면, 코드가 앱이 아니라 엣지에서 설정됐을 수 있습니다 — CDN의 리다이렉트·HTTPS·trailing-slash 정규화 규칙을 확인하세요. 이들은 흔히 301·302를 기본값으로 쓰고, 오리진이 보기도 전에 POST 본문을 벗겨냅니다.
관련 도구
관련 가이드
가이드 공유