Choosing 308 vs 301 for Method-Preserving Redirects
Problem Statement
You are redirecting an endpoint that receives POST requests — a form action, webhook, or API path — and need to know whether a 301 or a 308 is correct. A 301 permits clients to convert the follow-up request to GET, silently dropping the POST body, whereas a 308 guarantees the method and body are preserved. This page sits under 301 vs 302 Decision Trees.
When to Use This Approach
The set of routes this applies to is usually small and always worth identifying explicitly rather than by assumption.
- The redirected path accepts POST, PUT, PATCH, or DELETE — not just GET page views.
- You are migrating a form action URL, webhook receiver, or API endpoint.
- A client reports that data “disappears” after a redirect — a classic 301 method downgrade.
- You want a permanent redirect that is byte-for-byte safe for request bodies.
- You are choosing the
permanentflag in a framework that emits 308 (Next.js, Nuxt).
Step-by-Step Instructions
1. Decide Method Preservation First
Answer this before the permanence question, because it eliminates half the grid immediately and because it is the answer people get wrong by assuming the default is safe. Ask whether the endpoint ever receives a non-GET method. If it only serves page views, 301 is fine and is the most widely cached. If it can receive POST/PUT/PATCH/DELETE, choose 308 so the method and body survive the redirect.
GET-only page move -> 301 (permanent, method may downgrade — irrelevant for GET)
POST/API/webhook move -> 308 (permanent, method + body preserved)
Establish the candidate set from evidence rather than from the codebase. Filter thirty days of access logs for non-GET methods, take the distinct paths, and that list is what needs method preservation — typically a handful of form handlers, one or two webhook receivers, and at least one integration endpoint nobody currently working on the project knew existed. Reasoning from the application code reliably misses the last category, because the callers live outside it.
Everything not on that list should stay on 301. Method preservation costs nothing on a route that only ever receives GET, but 308 has patchier support among older proxies, corporate middleboxes and less common crawlers than 301 does, and there is no benefit to trading that away for routes that will never exercise the feature.
2. Configure 308 in Nginx
Nginx return accepts any status code, so emit 308 explicitly. Append $is_args$args to carry the query string, and keep the rule in the matching location.
# Permanent, method-preserving redirect for a moved form/API endpoint
location = /submit {
return 308 /api/v2/submit$is_args$args;
}
3. Configure 308 in Apache
Apache RewriteRule takes a numeric status with R=308. Use it where the moved path may receive POST.
# 308 keeps the POST body intact across the move
RewriteEngine On
RewriteRule ^/submit$ /api/v2/submit [R=308,L,QSA]
Both server examples issue the code directly rather than through a rewrite, which matters more here than for ordinary redirects. A rewrite-based rule that happens to emit the right status is easy to write and easy to have subtly wrong about the method, whereas an explicit return 308 or [R=308,L] states the intent unambiguously and is what a reviewer can check at a glance. Keep these rules separate from the bulk legacy map as well — they are a small, high-consequence set that deserves its own file and its own tests.
4. Map Framework “permanent” Flags Correctly
Modern frameworks emit 308 for permanent redirects. If a tool’s permanent: true produces 308 (Next.js, Nuxt routeRules can target either), confirm it matches your method requirement; for stack-specific syntax see CMS & Framework Routing Changes.
// next.config.js — permanent: true emits 308, which is what an API move wants
{ source: '/submit', destination: '/api/v2/submit', permanent: true }
The full picture is a two-by-two, and seeing it that way removes most of the difficulty. There are two independent questions — is the move permanent, and must the request method survive — and the four HTTP codes are simply the four answers.
Worked Example
The scenario below is a form endpoint moving during a domain change — the most common situation in which this distinction actually bites.
A webhook receiver moves from /submit to /api/v2/submit. With a 301, some clients re-issue the follow-up as GET and the JSON body is lost:
$ curl -sIL -X POST https://www.example.com/submit
HTTP/2 301
location: https://www.example.com/api/v2/submit
# client re-sends as GET -> body dropped, 400 at target
Switching the rule to 308 preserves the POST and its body:
$ curl -sIL -X POST https://www.example.com/submit
HTTP/2 308
location: https://www.example.com/api/v2/submit
HTTP/2 200
The receiver now gets the original POST body at the new path in a single hop.
The failure a 308 prevents is worth seeing explicitly, because it produces no error anywhere and looks to the user like a form that simply did nothing.
Test with a real POST carrying a real body, and assert on the outcome rather than the status. A verification that follows the redirect and checks for a 200 passes identically whether the payload arrived or was discarded, which is precisely the failure 308 exists to prevent. Send the request, follow the redirect, and then confirm that the thing the request was supposed to create actually exists — a row in the database, a record in the queue, a webhook acknowledged. That is the only check that distinguishes the two cases.
Verification
- Confirm the status code is 308, not 301:
curl -sI -X POST https://www.example.com/submit | grep -i '^HTTP'. - Send a real POST body with
curl -X POST -d '{"k":"v"}'and confirm the target processes it (200/201), proving the body survived. - Check the query string is preserved on the
Locationheader for parameterised endpoints.
FAQ
What is the difference between 301 and 308? Both are permanent. A 301 historically allows clients to change the follow-up request method to GET, which drops a POST body; a 308 forbids that, preserving the method and body. For GET-only page moves they behave the same for SEO.
Do search engines treat 308 like 301? Yes. Google treats 308 as a permanent redirect equivalent to 301 for ranking and consolidation. Use 308 specifically when method and body preservation matters; use 301 for ordinary GET page moves where it has the widest caching support.
Is 308 safe to use for ordinary pages as well?
Safe, but pointless, and it carries a small compatibility cost that a 301 does not. Support for 308 is good in modern browsers and patchy in older proxies, some corporate middleboxes, and an assortment of bots and link-checkers — none of which matters for an endpoint receiving form submissions from your own application, and all of which matters for a public page you want widely followed. Use 301 for anything that serves HTML to the open web, and reserve 308 for the routes that genuinely carry a request body.
How do I find which routes actually need method preservation?
Read the access logs for non-GET methods rather than reasoning about the application. Filter for POST, PUT, PATCH and DELETE, take the distinct paths, and that list is your candidate set — usually much shorter than expected and containing one or two endpoints nobody remembered, typically a webhook receiver or a legacy integration. Reasoning from the codebase reliably misses those, because the callers are outside it.
What does a framework’s permanent: true flag actually emit?
Depends on the framework, which is exactly why it is worth checking rather than assuming. Some emit 308 for permanent: true and 307 for false, preserving the method in both cases; others emit 301 and 302. Both behaviours are defensible and they are not interchangeable, so a config written on the assumption of one and deployed on the other silently changes how form submissions behave. Send an actual POST through the deployed rule and read the status code that comes back.
Does a 308 preserve authentication headers and cookies as well?
The method and body yes, everything else depends on the client and on whether the redirect crosses an origin. Browsers will not forward credentials across a cross-origin redirect regardless of status code, and many HTTP clients drop Authorization headers on any redirect as a deliberate security measure. That makes cross-origin 308 redirects an unreliable mechanism for authenticated API endpoints specifically — the method survives and the credentials do not, so the request arrives well-formed and unauthorised. Where an authenticated endpoint has to move across origins, update the callers rather than redirecting them.
Related
← Back to 301 vs 302 Decision Trees