skip to content

HTTP added status codes 307 and 308 alongside the older 301 and 302. What problem do they solve, and what happens to the request method and body when a client follows each of the four?

level: middleimportance: must knowfreq 55%

answer

  1. 307 = 302 with method preserved
  2. 308 = 301 with method preserved
  3. 301/302: POST→GET rewriting tolerated by RFC
  4. Preserving requires a replayable body
  5. Authorization often stripped cross-origin

basics

~20 s

301 and 302 were widely implemented by rewriting POST to GET and dropping the body, so method preservation became unpredictable. 307 (temporary) and 308 (permanent) forbid changing the method: the client re-sends the same method and body to the new URL.

solid answer

~60 s

**The problem:** early browsers followed 301 and 302 on a POST by issuing a GET to the new URL and discarding the body. That contradicted the specification, but it was universal enough that RFC 9110 now says a user agent **may** change the method for 301 and 302. So with those codes, whether your POST survives is client-dependent — unacceptable for APIs. **The fix** is two codes that map onto the same permanent/temporary axis but pin the method: | Code | Duration | Method/body | |---|---|---| | 301 | permanent | may be rewritten to GET | | 302 | temporary | may be rewritten to GET | | 307 | temporary | **preserved** | | 308 | permanent | **preserved** | So 308 is "301 done right" and 307 is "302 done right". Following a 307 on a POST means re-sending the same body to the new URL. Practical consequences: the client must be able to **replay the body**, so streaming or one-shot bodies break; and many clients strip `Authorization` when the redirect crosses origins. For anything non-GET, prefer 307/308 — or avoid redirecting at all.

code

http · 17 lines
http
POST /v1/orders HTTP/1.1
Host: api.example.com
Content-Type: application/json
Content-Length: 27

{"sku":"A-1","quantity":3}

HTTP/1.1 307 Temporary Redirect
Location: https://api-eu.example.com/v1/orders
Content-Length: 0

POST /v1/orders HTTP/1.1
Host: api-eu.example.com
Content-Type: application/json
Content-Length: 27

{"sku":"A-1","quantity":3}

go deeper

for a junior

Recall the grid: 307 is a temporary redirect that keeps the method, 308 is the permanent one, and both exist because 301 and 302 could turn a POST into a GET.

for a middle

Explain the historical POST-to-GET rewriting and that RFC 9110 tolerates it, then map all four codes on the permanent/temporary and preserve/rewrite axes including cacheability.

for a senior

Bring in the client-side costs: replayable bodies and buffering, Authorization stripping across origins, doubled latency, and the advice to migrate write endpoints rather than redirect them.

for a principal

Argue that redirects are a poor migration mechanism for write traffic because correctness depends on client library behaviour you do not control; treat 307/308 as a compatibility net while callers are moved, with observability on how many still take the redirect.

## Why two extra codes exist The original HTTP/1.0 text said a client following 301 or 302 should re-issue the *same* method. Browsers did not do that. Faced with a POST that received a 302, they issued a GET to the `Location` URL and threw the body away — largely because that is what web forms needed, and because re-POSTing to an unexpected URL felt dangerous. The behaviour became so entrenched that the specification eventually documented it: RFC 9110 states that for historical reasons a user agent **may** change the request method from POST to GET when following 301 or 302. "May" is the killer word — the outcome is not determined by the protocol, so a server sending 302 to a POST cannot know what will arrive next. HTTP/1.1 introduced **303 See Other** (always switch to GET) and **307 Temporary Redirect** (never switch) to make both intentions explicit. The permanent counterpart to 307 was missing until **RFC 7538** added **308 Permanent Redirect** in 2015. ## The four-way grid Two independent axes: is the move permanent, and is the method preserved? - **301 Moved Permanently** — permanent, method rewriting tolerated, cacheable by default. - **302 Found** — temporary, method rewriting tolerated, not cacheable by default. - **307 Temporary Redirect** — temporary, method and body **must** be preserved, not cacheable by default. - **308 Permanent Redirect** — permanent, method and body **must** be preserved, cacheable by default. (303 See Other occupies the third corner: it *mandates* the switch to GET rather than tolerating it.) ## What preservation actually costs the client "Re-send the same request to a new URL" sounds free. It is not. **The body must be replayable.** An HTTP client that streamed a request body from a socket, a generator, or a non-seekable file cannot reproduce it. Many client libraries therefore refuse to follow a 307/308 on a streamed body, or must buffer bodies in memory to make redirects possible — a real memory consideration for large uploads. Expect explicit errors like "cannot rewind request body". **Credentials may be dropped.** Most clients strip the `Authorization` header when a redirect crosses to a different origin, to avoid leaking a bearer token to a host the user never chose. Cookies are governed by their own domain scoping and likewise may not follow. A cross-origin 307 on an authenticated POST can therefore arrive unauthenticated and return 401. **Non-idempotent replay.** A POST that is followed to a new location has now been *sent twice as far as the network is concerned* if the first attempt partially reached a server. This is why redirecting non-idempotent requests deserves care rather than being treated as routine. **Browser friction.** Historically some browsers prompted the user before re-submitting a POST to a different URL on 307. Modern browsers generally follow silently for same-origin cases, but CORS rules apply cross-origin: a preflighted request that is redirected has extra constraints, and browsers restrict redirects during preflight entirely. ## When to use which - **GET traffic, final move:** 301 (or 308; 301 remains the convention for content and SEO, and tooling understands it best). - **GET traffic, conditional move:** 302. - **API endpoints where clients may send POST/PUT/PATCH/DELETE, permanently relocated:** 308. - **API endpoints temporarily relocated — failover, maintenance routing, regional pinning:** 307. - **After processing a POST, when you want the client to fetch a result page:** 303 (see the Post/Redirect/Get pattern). Best of all for an API: **do not redirect writes**. A redirect on a POST doubles latency, depends on client library behaviour you do not control, and risks credential stripping. Publish the new endpoint, migrate callers, and keep the redirect as a safety net rather than the mechanism. ## Compatibility 307 has been supported for decades. 308 is newer (2015) and was missing from some very old clients and a few embedded or legacy HTTP stacks, where it may surface as an unhandled status rather than a followed redirect. On the modern web both are safe; on a fleet of unknown or ancient clients, verify before relying on 308. ## The sentence that wins the question "307 and 308 exist because 301 and 302 let the client silently turn my POST into a GET and drop the body; 307 is the temporary form that forbids that, 308 is the permanent form, and the cost of preservation is that the client must be able to replay the body and may drop `Authorization` across origins."

  • A client library reports "cannot follow redirect: request body is not replayable" on a 308. What is happening and how do you fix it?
    The original request body was streamed from a source that cannot be rewound — a pipe, a generator, or a non-seekable stream — so the client cannot reproduce it for the second request. Options are to buffer the body in memory or a temp file so it can be replayed, to disable automatic redirect following and have the application resend deliberately, or best of all to stop redirecting writes and point callers at the final URL. Buffering has a real memory cost for large uploads.
  • Why might an authenticated POST that receives a 307 to a different domain come back with 401?
    Most HTTP clients and all browsers strip the Authorization header when a redirect crosses to a different origin, so the credential is not leaked to a host the caller never selected. Cookies follow their own domain scoping and usually will not be sent either. The second request therefore arrives anonymous. The remedy is to avoid cross-origin redirects for authenticated calls and update the client's base URL instead.
  • If 308 is the technically cleaner permanent redirect, why is 301 still the default for websites?
    For ordinary GET page traffic there is no method to preserve, so the two behave identically in the browser. 301 has thirty years of universal support across crawlers, proxies, CDNs, and analytics tooling, and search engines have long-established handling for it. 308 is the right choice specifically when non-GET requests may hit the URL, such as an API endpoint that has permanently moved.

301 and 302 are like a forwarding clerk who opens your parcel, keeps the address slip and bins the contents; 307 and 308 forward the sealed parcel exactly as it arrived.

saying these in an interview costs you the question

  • Saying 301 and 302 always preserve the request method
  • Thinking 307 differs from 302 in duration rather than in method preservation
  • Believing 308 responses are non-cacheable like 307
  • Assuming a preserved POST always still carries its Authorization header after a cross-origin redirect
  • Treating a redirect as a free, zero-cost way to migrate write endpoints

context