HTTP 302 Found

Found

HTTP 302 is Found: the resource is temporarily at the URL in Location, and the client should keep using the original URL for future requests. HTTP/1.0 called it Moved Temporarily; RFC 9110 section 15.4.3 defines it now. It’s the default redirect almost everywhere, which is why it’s the one most often misused.

The POST problem

RFC 9110 lets a user agent change POST into GET when following a 302, “for historical reasons”, and browsers do: the Fetch standard turns a POST into a GET with no body after a 301 or 302. So a 302 doesn’t say what happens to the method, and two later codes do:

302 Found                temporary; POST may come back as GET
303 See Other            temporary; always GET the new URL
307 Temporary Redirect   temporary; same method, same body

The post/redirect/get pattern, where a form submission redirects to a result page so that reloading doesn’t submit twice, is what 303 is for. A temporary move of an endpoint that takes POST is 307. A move for good is 301 or 308.

Who sends it by default

res.redirect() in Express, redirect() in Flask and Django, nginx’s rewrite ... redirect, and PHP’s header('Location: ...') all send 302 unless told otherwise. Login flows run on it: Django’s @login_required answers an anonymous visitor with a 302 to the login page rather than a 401, and the OAuth 2.0 examples in RFC 6749 send the browser back to the app with a 302.

Caching

302 isn’t heuristically cacheable: with no Cache-Control or Expires, caches don’t keep it, so every request goes to the original URL first. That’s the point of a temporary redirect. If you want it reused for a while, send Cache-Control: max-age=... with it.

Search engines

Google follows 302 and 307 and treats them as a weak signal that the target should be canonical, against the strong signal of 301 and 308. If the move is permanent, say so with a 301; a redirect that has stayed “temporary” for years is a permanent one with the wrong code.

In your stack

res.redirect('/login');                           // Express: 302
res.redirect(303, `/orders/${order.id}`);         // after a form POST
location = /sale { return 302 /spring-sale; }
return redirect(url_for("login"))                 # Flask: 302
return redirect(url_for("order", id=order.id), code=303)
http.Redirect(w, r, "/login", http.StatusFound)
http.Redirect(w, r, "/orders/4182", http.StatusSeeOther) // after a POST

Search 301 302 307 308 in the tool below to compare the four redirects side by side.

Try it in the HTTP Status Codes

302 Found

  1. 302Found3xx Redirection

    The resource is temporarily at another URL; the old one stays valid and will serve again.

    When to use it

    Sending a signed-out visitor to a sign-in page, or any redirect whose target will change back.

    Common mistakes

    Leaving it in place for years, and expecting caches to follow it: 302 is not cacheable by default, so clients keep hitting the original.

    Not cacheable by defaultRFC 9110Formerly Moved Temporarily

Every code, by class

Codes marked unofficial come from one server’s own convention — nginx, Cloudflare, cPanel, Microsoft — and are not in the IANA registry.

Runs in your browser. Nothing you enter is sent anywhere.