Understanding HTTP 308 Permanent Redirect: Method-Preserving Redirects Done Right
The 308 Permanent Redirect is the modern, method-preserving sibling of the 301. For an ordinary "this page moved" redirect, the two are interchangeable. But the moment a POST, PUT, or DELETE is involved, the difference becomes load-bearing — and choosing 301 out of habit can quietly turn a POST into a GET and break your API. Our 301 redirects post covers the SEO mechanics of permanent redirects in depth; this one is about the part 301 can't guarantee, and when you actually need 308.
What Is a 308?
A 308 tells the client the resource has permanently moved to the URL in the Location header — and, critically, that it must repeat the request exactly as it was, including the method and body.
The 308 (Permanent Redirect) status code indicates that the target resource has been assigned a new permanent URI and any future references to this resource ought to use one of the enclosed URIs. [...] This status code is similar to 301 (Moved Permanently), except that it does not allow changing the request method from POST to GET. — RFC 7538, Section 3
A bare 308 looks like this:
HTTP/1.1 308 Permanent Redirect
Location: /new-permanent-endpointSame shape as a 301 — the difference is entirely in how the client is required to behave afterward.
308 vs 301: The Method-Preservation Difference
Here's the whole point of 308 in one paragraph. A 301 permits the client to change the request method from POST to GET when it follows the redirect. This isn't a bug — it's baked into how browsers have behaved since the 1990s, when many clients silently downgraded redirected POSTs to GETs. A 308 forbids that: the method and body must survive the redirect untouched.
So:
- Page-to-page GET redirects — 301 and 308 are functionally identical. The browser was going to issue a
GETeither way. POST/PUT/DELETEredirects — they diverge. A 301 might turn yourPOST /api/ordersintoGET /api/orders(dropping the body entirely); a 308 guarantees it staysPOST /api/orderswith the body intact.
If you've ever seen a form submission or API call mysteriously lose its payload after a redirect, an over-eager 301 (or 302) is the usual suspect. 308 is the fix.
The Full Redirect Matrix
Two axes decide which redirect you want: is the move permanent, and must it preserve the method?
| Status | Permanent? | Preserves method? | Use when |
|---|---|---|---|
| 301 Moved Permanently | Yes | No (may →GET) | Permanent move, GET pages, SEO migrations |
| 302 Found | No | No (may →GET) | Temporary move (A/B test, maintenance) |
| 303 See Other | No | No (forces GET) | Post/Redirect/Get after a form submission |
| 307 Temporary Redirect | No | Yes | Temporary move that must keep the method |
| 308 Permanent Redirect | Yes | Yes | Permanent move that must keep the method (APIs) |
Read the bottom two rows as the method-preserving pair: 307 is to 302 as 308 is to 301. And 303 is the deliberate odd one — it forces a GET, which is exactly what you want for the Post/Redirect/Get pattern after a form submit.
When to Use 308
Reach for 308 when the move is permanent and the method matters:
- Permanently relocated API endpoints that accept
POST/PUT/PATCH/DELETE. Clients should update their references, and their request bodies must survive the hop in the meantime. - HTTPS upgrades for non-GET traffic. If you redirect all HTTP to HTTPS and any of that traffic is
POST(webhooks, form posts, API calls), a 301 risks dropping the body. 308 keeps it. - Permanent URL restructuring where you can't assume every caller is using
GET.
When a plain 301 is the better call:
- GET-only page moves — content migrations, vanity URLs, trailing-slash normalization. 301 is universally understood by every crawler, proxy, and ancient client, and method preservation is irrelevant for a
GET.
For SEO, the two are equivalent (more below), so the deciding factor is almost always what HTTP methods hit this URL.
Implementation Examples
NGINX
# Permanent, method-preserving redirect
location /old-api {
return 308 /new-api;
}
# HTTP→HTTPS keeping POST bodies intact
server {
listen 80;
server_name api.example.com;
return 308 https://$host$request_uri;
}Apache (.htaccess)
# Single endpoint
Redirect 308 /old-api https://example.com/new-api
# With rewrite rules
RewriteEngine On
RewriteRule ^old-api/(.*)$ https://example.com/new-api/$1 [R=308,L]Next.js (next.config.js)
This one trips people up — including a simplification in our own 301 post. Next.js's redirects() does not emit 301/302. It emits the method-preserving codes:
module.exports = {
async redirects() {
return [
{
source: "/old-api/:path*",
destination: "/new-api/:path*",
permanent: true, // → 308 (NOT 301)
},
{
source: "/beta",
destination: "/preview",
permanent: false, // → 307 (NOT 302)
},
];
},
};So permanent: true gives you a 308 and permanent: false gives you a 307. If you specifically need a 301 (say, for a tool that mishandles 308), use statusCode: 301 instead of permanent — but for almost all cases, Next.js's 308/307 default is the more correct behavior.
Node.js / Express
app.all("/old-api", (req, res) => {
res.redirect(308, "/new-api");
});Use app.all (not app.get) so the redirect applies to every method — the whole reason you picked 308 is to handle non-GET requests.
Caching Behavior
Like 301, a 308 is treated as permanent and cached aggressively. Once a browser or proxy sees a 308 from /old to /new, it may skip the server entirely on future requests and go straight to /new — by design.
That carries the same recovery trap as 301: if you ship a wrong 308, fixing it server-side won't immediately help clients who already cached the bad redirect. Protect yourself the same way:
- Test redirects in an incognito/private window before deploying.
- Set a sane
Cache-Controlso caches will eventually re-check:
HTTP/1.1 308 Permanent Redirect
Location: https://example.com/new-api
Cache-Control: max-age=86400A 24-hour window gives you the permanence benefits while leaving room to recover from a mistake.
SEO
For search engines, 308 and 301 are equivalent. A 308:
- De-indexes the old URL and replaces it with the destination
- Passes link equity (ranking signals) to the new URL
- Acts as a strong canonical signal
Here's the subtle part: crawlers issue GET requests, so the method-preservation feature that makes 308 special is invisible to them. That means you should never choose 308 over 301 for SEO reasons — they behave identically in the index. Choose 308 over 301 for user and API behavior (keeping POST bodies intact), not for rankings.
Browser & Client Support
308 was standardized in 2015 (RFC 7538) and is now universally supported across modern browsers, HTTP libraries, and CDNs. The historical "not every client understands 308" caveat is effectively obsolete for the public web.
The exception worth a thought: very old or minimal HTTP clients (legacy embedded devices, hand-rolled clients, ancient libraries) may not recognize 308 and could fail rather than follow it. If you're redirecting traffic from an unknown long tail of such clients, that's the one scenario where falling back to 301 — and accepting the method-change risk — can be the pragmatic choice.
Common Pitfalls
- Using 301 for an API endpoint that needs POST preserved. The redirect "works" in a browser test (GET), then silently drops bodies for real
POSTtraffic in production. Use 308. - Assuming
permanent: truemeans 301. In Next.js it's a 308; in other frameworks it varies. Check what your framework actually emits before relying on a specific code. - Redirect chains and loops. 308 caches as hard as 301, so a chain of permanent hops is even stickier. Point the original redirect straight at the final destination — see the 301 post for detection techniques with
curl -sIL. - Dropping query strings. Make sure your 308 rule carries through
?utm_source=...and friends, or you'll lose attribution data on every redirected request. - Forgetting internal links. As with any redirect, update your own links to point at the new URL directly. The redirect is a safety net for callers you don't control, not a substitute for fixing references you do.
Wrapping Up
308 is "301 that keeps its promises about the HTTP method." The rules of thumb:
- For GET page moves, 301 and 308 are interchangeable — pick 301 for maximum tooling compatibility
- For permanent moves of
POST/PUT/DELETEendpoints, use 308 so the method and body survive - Remember the matrix: 307 is the temporary method-preserving redirect, 308 is the permanent one
- Next.js
redirects()emits 308 (permanent: true) and 307 (permanent: false) — not 301/302 - 308 caches aggressively like 301; test in incognito and set a sane
Cache-Control - For SEO, 308 == 301; choose 308 for client behavior, never for rankings
For more, see our pages on 308 Permanent Redirect, 301 Moved Permanently, and 307 Temporary Redirect. The companion understanding 301 redirects post goes deep on migrations and SEO equity, and you can compare 301 vs 308 side by side.