301 vs 302: The Redirect That Breaks Everything

The difference between 301 and 302 isn't 'permanent vs temporary.' It's two unrelated decisions the codes quietly bundle together — and a 301 you set by accident can live in a stranger's browser cache forever.

Here is a mistake people make exactly once. You’re moving a site to HTTPS, you set up a redirect from http:// to https://, and because it’s a permanent move you reach for the code that means permanent: 301. It works. Months later you need to undo it — maybe the HTTPS cert broke, maybe you’re debugging, maybe you just want plain HTTP back for a minute. You change the server config. And the redirect keeps happening anyway, for users who visited before, with no way for you to reach into their browsers and stop it.

That’s not a bug. That’s 301 doing exactly what it promises. The trouble is that almost nobody reads the promise carefully, because “301 = permanent, 302 = temporary” is one of those facts so easy to memorize that you never look underneath it. And what’s underneath is that 301 versus 302 isn’t one decision. It’s two, fused into a single number, and most of the breakage comes from the one you weren’t thinking about.

The permanence you wanted, and the permanence you got

When you pick 301 for SEO reasons, the permanence you have in mind is search engines: tell Google the page moved for good so it transfers the ranking and updates its index. Fine. That part works.

But “permanent” doesn’t only talk to crawlers. It talks to browsers, and browsers take it literally. A 301 is cacheable by default — no Cache-Control, no Expires, nothing required. Chrome and the others will store it and, for a returning visitor, perform the redirect without asking your server again. The redirect has left your control. It now lives on someone else’s disk, and your config change is invisible to it.

So the asymmetry is brutal. The permanence you wanted lives in Google’s index, which you can influence and which recovers. The permanence you got lives in every past visitor’s browser cache, which you cannot influence at all. A 302, by contrast, is the resolver re-checking with you each time — temporary in exactly the way that lets you change your mind. The single safest habit in this whole area: while you are still unsure whether a move is forever, a 302 keeps the decision in your hands. Promote it to 301 once you’re certain and willing to live with “forever” meaning forever.

The other decision hiding in the number

Caching is the famous trap. The subtler one is what happens to the HTTP method, and this is where redirects silently eat data.

Say a user submits a form — a POST with a body — to a URL that answers with a redirect. What method does the browser use for the redirected request? You’d think it obviously reuses POST. For 301 and 302, it often doesn’t. It quietly switches to GET and drops the body. The form submission evaporates; the user lands on a page having sent nothing. An API client following a 302 loses its payload the same way.

This isn’t browsers being buggy. It’s history calcified into the spec. In HTTP/1.0 (RFC 1945), 302 was called “Moved Temporarily,” and the text said clients shouldn’t change the method — but every browser changed POST to GET anyway, and enough of the web came to depend on that behavior that it couldn’t be undone. HTTP/1.1 tried to clean up the mess not by fixing 302 but by adding two unambiguous codes around it: 303 See Other, which always means “go GET this other thing,” and 307 Temporary Redirect, which guarantees the method and body are preserved. 302 itself was renamed “Found” and left deliberately vague. RFC 9110, the current HTTP spec from 2022, still encodes the ambiguity in plain sight: for 301 and 302 it says a user agent MAY change a POST to a GET “for historical reasons.” MAY. The spec is telling you it doesn’t know what your browser will do.

Years later, 308 Permanent Redirect (RFC 7538, 2015) filled in the last corner. Now there’s a clean grid: 301 and 308 are permanent; 302 and 307 are temporary; the 307/308 pair preserves the method, the 301/302 pair might not. Four codes, two independent axes — cacheable-or-not and method-preserving-or-not — finally pulled apart into separate choices instead of bundled into a folklore pair.

What this actually means for you

The reason this matters isn’t pedantry about status codes. It’s that the two classic codes make two decisions for you, and you were probably only thinking about one.

When you type 301, you are also signing up for “uncacheable from your side, possibly forever, in clients you’ll never see.” When you type 302 in front of a form handler, you are also signing up for “the POST body may silently vanish.” Neither of those is in the name. “Permanent” and “temporary” describe the SEO half and stay completely silent about the caching-revocability half and the method half.

So the rule I’d actually give: default to 302 while anything is in flux, because reversibility is worth more than you think and costs almost nothing. Reach for 301 only when the move is genuinely forever and you’ve made peace with not being able to take it back from browsers. And the instant a redirect sits in front of a POST — a login, a form, an API — stop using the classic pair entirely and use 307 or 308, the ones that promise in writing not to turn your POST into a GET. The redirect that breaks everything is the one you chose by its nickname instead of by what it does.

Continue the conversation

← Back to Blog