308 Redirect Explained: How It Differs From 307, 301, 302

By Selim Aydin ·

308 Redirect vs 307, 302 and 301: The Method Preservation Difference

A 308 redirect isn't a fancier version of a 301. It solves a problem the 301 was never built to solve: keeping a POST, PUT, or DELETE request intact after your server tells the client to go somewhere else. If you've ever redirected an API endpoint, a webhook, or a form handler and watched the payload vanish on the other side, you've already met the gap between "permanent redirect" and "permanent redirect that doesn't quietly rewrite your request." This piece walks through what a 308 permanent redirect actually guarantees, how it differs from a 307 http status code, and when either one should replace the 301/302 pair most people reach for out of habit.

The One Difference That Actually Matters

Three separate systems touch every redirect: the server that decides which status code to send, the client that decides how to follow it, and (for public pages) the search engine crawler that decides what the redirect means for indexing. Most confusion about 307 and 308 comes from mixing these up. Get the status code right at the server and you've only solved a third of the problem, because the client still has to honor it correctly.

The symptom: a POST that arrives as an empty GET

Here's the scenario that actually brings people to this topic. A team redirects an old checkout endpoint to a new one, or points a webhook receiver at a new path after a platform migration, or adds a "force www" rule that's supposed to apply cleanly across the whole domain.

The redirect works fine in a browser. Then someone notices the payment form silently fails, or a webhook relay starts throwing 405 Method Not Allowed, or an API client gets a 200 back with none of the data it sent. The form data is gone. The POST somehow became a GET.

That's not a bug in the receiving application. It's the redirect doing exactly what 301 and 302 have always been allowed to do.

Why 301 and 302 let this happen

Neither the 301 (Moved Permanently) nor the 302 (Found) status code has ever guaranteed that a client preserves the original request method or body when it follows the redirect. For decades, browsers have handled a POST-plus-301 or POST-plus-302 by converting the follow-up request into a GET and dropping the body, a behavior baked in largely for compatibility with old web forms. MDN documents this directly: most user agents treat a 302 the way they'd treat a 303 (See Other), rewriting the method to GET unless the original request was already a GET, in which case there's nothing to rewrite and no visible problem (MDN, 307 Temporary Redirect).

This is why a "force HTTPS" or "add www" rule written for ordinary page traffic can quietly break an API. The rule only ever gets tested by loading pages in a browser, which sends GET requests almost exclusively. Nobody notices the method-rewrite behavior until a form, a webhook, or a script sends something other than GET through the same rule.

What 307 and 308 guarantee instead

307 (Temporary Redirect) and 308 (Permanent Redirect) exist specifically to close that gap. Per RFC 9110, the current HTTP Semantics standard from the IETF, a user agent following either code "MUST NOT change the request method" and must preserve the message body (RFC 9110, sections 15.4.8-15.4.9).

That's not a suggestion buried in a note. It's normative language, the same strength of requirement as "servers MUST NOT" clauses elsewhere in the spec. A compliant client that receives a 308 on a POST is required to resend that POST, with its body, to the new location.

308 is to 301 what 307 is to 302: same permanence, the same default caching behavior as their 3xx siblings, but with the method-preservation guarantee 301 and 302 never had. RFC 9110 folds in and clarifies work that was previously scattered across RFC 7231 and the older RFC 7238 (which introduced 308 in 2014 for exactly this reason: 301 had no method-preserving permanent option, and 302 couldn't reliably fill the temporary version of that role because of how widely clients had already diverged from spec on it).

301 vs 302 vs 307 vs 308: the comparison table

Laid out side by side, the pattern is easier to hold in your head:

CodePermanenceMethod preservedBody preservedDefault cachingTypical use
301 Moved PermanentlyPermanentNot guaranteedNot guaranteedCacheablePage moved for good, request is GET
302 FoundTemporaryNot guaranteed (treated like 303 in practice)Not guaranteedNot cacheable by defaultShort-term redirect, request is GET
307 Temporary RedirectTemporaryGuaranteedGuaranteedNot cacheable by defaultTemporary move where method/payload must survive
308 Permanent RedirectPermanentGuaranteedGuaranteedCacheablePermanent move where method/payload must survive

The "not guaranteed" cells for 301 and 302 aren't a spec defect, to be fair. Early HTTP client behavior around those two codes was never pinned down tightly enough to change without breaking the web, so 307 and 308 were added as new codes instead of trying to retrofit stricter rules onto old ones.

308 vs 301: are they really interchangeable for SEO?

This is where a lot of site owners get nervous, and it's worth addressing head on rather than dodging it. Google's own documentation on canonicalization lists redirects among the signals its indexing pipeline uses to decide which URL is the canonical one, and a permanent status code is read as a stronger signal toward the destination URL than a temporary one (Google Search Central, canonicalization documentation). Since RFC 9110 defines 308 with the same permanence semantics as 301, just with the method-preservation guarantee layered on top, switching a permanent page-move redirect from 301 to 308 is not treated as a downgrade. Both are read as a permanent move by Google's crawling and indexing systems.

None of that makes 308 the "better" choice for a normal page move. For an ordinary URL change on a page fetched with GET, a plain 301 remains the standard recommendation: it's universally supported, well understood by every crawler and CDN, and there's no method-rewrite risk to guard against when the original request was GET in the first place. Reach for 308 when the thing being redirected can be requested with something other than GET, not as a general upgrade to 301. For the full walkthrough on choosing between the two everyday codes, see 301 vs 302 redirects: which one should you use.

Decision checklist: choosing the right code

A short set of questions settles almost every case:

  • Is the move permanent, and is the resource only ever fetched with GET? Use 301.
  • Is the move temporary, and is the resource only ever fetched with GET? Use 302.
  • Is the move permanent, and could the resource be fetched with POST, PUT, PATCH, or DELETE? Use 308.
  • Is the move temporary, and could the resource be fetched with POST, PUT, PATCH, or DELETE? Use 307.

A few concrete cases where the answer is almost always 307 or 308: API endpoint versioning (a client library posting JSON shouldn't lose its request body mid-flight), webhook relays (a payment provider or CI system replaying a POST to a new listener URL), checkout and payment form handlers, and resumable file upload endpoints that rely on PUT or PATCH. If any of those pass through a redirect rule written for whole-domain HTTPS or www enforcement, check what method that rule is actually being hit with. It's rarely just GET.

The client-side gotcha: spec-compliant isn't always client-compliant

Sending the right status code from your server is necessary but not sufficient, because the client still has to implement RFC 9110 correctly. It doesn't always. An older, widely reported issue in Python's popular requests library documented exactly this: when following a 307 redirect on a POST or PUT, some versions silently dropped the request body instead of resending it, turning a spec-compliant 307 into a bodyless request on the client side (psf/requests, GitHub issue #1084).

Nobody's server was wrong here. The client library just hadn't caught up to the requirement.

That's the uncomfortable part of this whole topic: you can send the technically correct status code and still get burned, because "the spec says MUST NOT change the method" and "every HTTP client in production actually does that" are two different claims. Browsers are generally reliable about 307/308 method preservation today. Older scripting libraries, some serverless runtimes, and internal tooling built years ago are the places I'd check first if a redirect looks right on paper but still breaks downstream.

How to verify what your server is actually sending

This is where guessing stops being useful and checking starts. Point a request at the redirect the same way your form, webhook, or API client would, then read the actual status code and headers back, hop by hop, rather than trusting what the rule was supposed to do. A quick curl -I tells you the immediate response code, but it won't show you a multi-hop chain or let you swap the request method the way a real client would.

Running the URL through httpcheck.tools gives you that full picture in one pass: every hop in the redirect chain, the exact status code at each step, whether it's a 301, 302, 307, or 308, and the response headers that determine caching behavior. The tool also lets you switch between GET and HEAD and select a user agent, which matters if you suspect a redirect rule behaves differently for a browser than it does for a script or a bot. If you're not sure what a particular status code in the chain actually means for your case, the site's own status code reference covers the full range, and the header-by-header guide is useful once you're past the redirect and checking what the destination is sending back.

As a default rule of thumb: stick with 301 and 302 for ordinary page moves where the request is GET, and switch to 308 or 307 specifically when the redirected resource gets hit with POST, PUT, or DELETE and the payload needs to survive the trip. Check the actual response rather than assuming the rule does what its name suggests. Redirect rules get copied between projects more often than they get re-read.

Frequently Asked Questions

What is a 308 redirect?

A 308 redirect is an HTTP status code that tells a client a resource has permanently moved to a new URL, while requiring the client to resend the original request method and body to that new location. It behaves like a 301 in terms of permanence and default cacheability, but unlike a 301, RFC 9110 requires that a client "MUST NOT change the request method" when following it, which is why it's the correct choice for permanently redirecting API endpoints, form handlers, or any URL that might be requested with something other than GET.

What is the difference between 307 and 302?

Both are temporary redirects with the same non-cacheable default behavior, but they differ on method and body preservation. A 302 has never guaranteed that a client keeps the original request method, and in practice most browsers convert a POST-plus-302 into a GET and drop the body, the same way they've long handled 301. A 307 closes that gap: RFC 9110 requires the method and body to be preserved, so a POST redirected with a 307 arrives at the new URL as a POST with its original payload intact.

Does Google treat 308 like 301?

Yes. Google's documentation on canonicalization lists redirects among the signals its indexing pipeline uses to pick a canonical URL, and a permanent redirect is read as a stronger signal toward the destination than a temporary one, regardless of whether the code is 301 or 308. Since RFC 9110 gives 308 the same permanence semantics as 301, just with method preservation added, switching a permanent redirect from 301 to 308 does not carry SEO risk; it's still read as a permanent move.

When does a POST request need a 307 or 308?

Whenever the redirected URL might be requested with a method other than GET and the request body needs to survive the redirect, meaning API endpoints, webhook receivers, payment and checkout form handlers, and resumable upload endpoints using PUT or PATCH. If the redirect is temporary, use 307; if it's permanent, use 308. Redirecting the same kind of resource with a plain 301 or 302 risks the client silently rewriting the POST into a GET and dropping the payload, which shows up downstream as missing form data, a bodyless request, or an unexpected 405 Method Not Allowed.