308 Permanent Redirect
Redirect
The target resource has been assigned a new permanent URI and any future references should use the new URI. Like 307, the client must not change the request method or body when following the redirect. 308 is the modern, strict-method equivalent of 301. It gives search engines the same canonical-update signal as 301 while guaranteeing API clients will not silently downgrade POST/PUT/DELETE to GET.
When does this happen?
Use 308 for permanent redirects where method preservation matters. The canonical example is an API endpoint that has moved permanently. POST /v1/old-endpoint should 308 to POST /v2/new-endpoint so clients reissue with the same body and the action completes. Search engines treat 308 the same as 301: they update the canonical URL and transfer link equity. Browsers cache 308s aggressively, similar to 301, so shipping the wrong destination is hard to undo in clients that have already seen it. The Location header is required. For HTML pages, 301 is still the more common choice simply because it is older and more widely understood by tooling, but 308 is strictly more correct anywhere a method might be non-GET. Older HTTP libraries (curl < 7.49, released May 2016, when RFC 7538 308 support landed; very old .NET) may not handle 308 correctly. If your audience includes legacy clients, test before shipping.
Common causes
- API endpoint relocated permanently from /v1 to /v2 and clients should reissue with the same method and body.
- Webhooks endpoint moved to a new path and incoming POSTs need to land at the new URL without losing the payload.
- Permanent move of a non-GET resource from one domain to another where the body must survive.
- RPC-style API path renamed and the server wants every old caller to silently migrate without code changes.
- Permanent failover of a write-heavy endpoint to a new region during a planned migration.
How to fix it
- Update clients to call the new URL directly so the 308 is a backstop, not a permanent extra round-trip on every request.
- Verify clients on the wire actually preserve POST/PUT. Test with curl, your SDK, and any third-party integrations before declaring the migration complete.
- Confirm the destination handler accepts the same method and request body shape. A 308 to a different schema will look like a success at the redirect layer but fail downstream.
- Audit clients for HTTP libraries that pre-date 308 support (curl < 7.49, released May 2016). Those will fall back to GET or error.
- Set the new URL as canonical in any sitemap or API documentation so search engines and developers stop discovering the old path.
- Like with 301, set a Cache-Control header on the 308 if you might need to undo it. Without one, browsers and intermediaries may cache the redirect indefinitely.
Real-world examples
- API v1 is sunsetted and POST /v1/orders should permanently move to /v2/orders.
- Server returns 308 with Location: /v2/orders. SDKs reissue POST with the same JSON body to the new path. The migration is invisible to callers.
- Webhook receiver moves from webhooks.example.com to events.example.com.
- Old URL returns 308. Senders that follow redirects reissue the POST with the same payload to the new host and webhook delivery succeeds.
- Domain consolidation merges api.legacy.com into api.example.com with the same path structure.
- Old domain returns 308 for every request. Clients preserve method and body and the legacy domain can eventually be retired.
- PUT endpoint /v1/configs/{id} renamed to /v1/settings/{id} after an internal refactor.
- Server returns 308 with the new Location. Clients PUT the same config body to the new URL and the update lands correctly.
Debugging
- Run curl -I -X POST https://example.com/v1/old and confirm the response is 308 with a Location header.
- Run curl -L -X POST -d 'payload' https://example.com/v1/old and verify curl follows with POST preserved (curl 7.49+ handles 308 by default; 307 has worked far longer).
- Inspect Cache-Control. 308s with no cache directives may be cached forever by browsers.
- In Chrome DevTools, the chained request should appear with the same Request Method as the original. If it changed, the client implementation is buggy.
How it differs from related codes
HTTP 301
Both are permanent. 301 historically allowed clients to downgrade POST to GET; 308 forbids it. Use 308 for APIs and 301 for plain HTML page moves where method is always GET.
HTTP 307
307 is temporary, 308 is permanent. Both preserve the method. Pick based on whether you intend search engines and clients to update the canonical URL.
HTTP 302
302 is temporary and may downgrade method. 308 is permanent and preserves method. Opposite on both axes. They are rarely interchangeable.
Related status codes
See HTTP 308 in your redirect chains?