# A summary of HTTP redirects

After discovering long ago that there’s more to HTTP redirects than just 301 and 302, I’ve often ended up scanning specs to remind myself which status code I really want to use. There isn’t actually a simple choice between “permanent” and “temporary.”

My aim here is to make life a little simpler by summarising things, especially since early implementations went against the spec. This is primarily based off the description of HTTP 1.1 in RFC 2616, except more representative of the way browsers actually work.

If you take one thing away from this, it’s hopefully that you shouldn’t trust either your browser or a spec.

For three main use cases, here are status codes I’d recommend:

• To redirect after handling form POST submissions, use a 303.
• If a URL changed and you want browsers to GET (and only GET) the new one, use a 301 if it’s permanent, or a 302 if it’s temporary.
• If a URL changed and you want to ensure any POST data also goes to the new resource, use a 307.

Or in other words:

• 301: Try again at this new location and remember this change.
• 302: Try again at this new location but keep coming back here just in case.
• 303: I’ve handled your request, now go here for a follow-up.
• 307: Whatever you’re trying to look at or submit changes to, go to this new place and try the same thing there.

A recent problem at work required a permanent change to URL structure. But the URLs in question were both GET and POST endpoints so a 301 — frequently thought of as the “permanent” redirect — would not have worked.

## Summary table

The 301 and 302 in this table are in bold to indicate that browsers implemented them incorrectly and this is now their de facto meaning.

Type Maintain HTTP method Change to GET
Temporary 307 302, 303
Permanent 308* 301

* 308 is from an Experimental RFC.

## Response caching

Each one also has its own cache restrictions.

• 301: the redirect is permanent and browsers re-request with a GET, so it is inherently cached.
• 302: can be cached based on Cache-Control and Expires headers.
• 303: “MUST NOT” be cached, which is one reason it’s good for redirecting after handling a form submission, since the resulting page will never be cached.
• 307: can be cached based on Cache-Control and Expires headers.
• 308: can be cached.

## Extra details

### 301 Moved Permanently

Originally intended to keep the HTTP method and to resubmit. Browsers implemented it as something like a permanent 303.

### 302 Found

Originally “302 Moved Temporarily” because it was supposed to preserve the HTTP method (POST, GET, etc.) and simply indicate where a resource moved. It was incorrectly treated as something like a 303, got a new name in HTTP 1.1 (“Found”) and sort of means temporary redirect. 307 was then created to cover temporary redirects with HTTP methods preserved.

### 303 See Other

Almost identical to how 302 works, except it has additional semantics and is mainly intended to be used after handling a POST request. Hence it is called “See Other” instead of referring to redirects. To quote the RFC: “The new URI is not a substitute reference for the originally requested resource.”

### 307 Temporary Redirect

The requested resource is temporarily at a different URL, so issue the same request to the new location. Pretty much what 302 was meant to be. Form submissions to /foo that redirect with a 307 response should cause an identical request but aimed at a different location.

### 308 Permanent Redirect

Proposed under an RFC that is currently experimental. Covers the remaining situation of a permanent version of 307.