ByteScope

302

How to fix HTTP 302 Found

3xx redirectionIETF standardRFC 9110

A temporary detour: the resource still belongs at the URL you asked for, but for now the server wants this request served from somewhere else — and your browser will take the detour without ever showing it to your code.

Why you are seeing 302, and what to do about it

The same three digits are three different problems depending on which side of the request you are on. Read the block that describes you.

Reproduce 302 with curl

Run this against the URL that failed. It prints the status without the error page, so you can see what the server said rather than what the browser rendered.

curl
curl -sS -D - -o /dev/null -L -d 'user=me' -w '\nfinal %{http_code} after %{num_redirects} hops at %{url_effective}\n' https://example.com/login

`-d` sends a real request body and makes the request a POST on its own — deliberately without `-X`, which curl documents as applying to every request in the chain and would therefore suppress the one rewrite this command exists to show. `-L` follows the chain, `-D -` dumps every hop's headers, and `-w` prints the final status, the hop count and the URL that answered. Four things in that dump decide what you do next. The first status line separates 302 from 303 and 307, which is the difference between "the method may change", "the next request is a GET" and "replay it unchanged". The `location:` value shows whether the application built the URL from the right request — `http://` on an HTTPS site, an internal hostname, or a port that only exists inside the container all point at a missing `X-Forwarded-Proto` or `X-Forwarded-Host`. `set-cookie:` on that same response is where a login loop is decided: a `Secure` cookie over plain HTTP, `SameSite=None` without `Secure`, or a `Domain` the destination does not share means the next hop arrives with no session and bounces again. And the final response's `content-type` is the giveaway your front end could not see: `text/html` where you asked for JSON is the login bounce. curl documents that it downgrades POST to GET when it follows a 301, 302 or 303 and keeps the method for any other 3xx, so re-running this with `--post302` and comparing what the server logs proves whether the method rewrite is what dropped your fields.

What 302 actually means

RFC 9110 §15.4.3 defines 302 as the target resource residing temporarily under a different URI, and adds the instruction that makes it different from 301: since the redirection might be altered on occasion, the client ought to continue to use the original target URI for future requests. The same section carries the historical caveat — a user agent MAY change the request method from POST to GET, and 307 (Temporary Redirect) is named as the code to use when that is undesired. What the specification does not say is as useful: 302 is absent from the list of heuristically cacheable status codes in RFC 9110 §15.1, so unless you attach an explicit `Cache-Control`, a 302 is not stored and every request asks again. That is the property that makes it the right redirect to iterate with. One more distinction is worth having straight before you write a login flow: §15.4.4 defines 303 (See Other) as "the server is redirecting the user agent to a different resource, as indicated by a URI in the Location field, which is intended to provide an indirect response to the original request", with the subsequent request specified to use GET. Most frameworks emit 302 for a post-redirect-get, and 303 is the code that actually means it.

Codes 302 gets mistaken for

Confused withHow to tell them apart
307307 (Temporary Redirect) is 302 with the method guaranteed. RFC 9110 §15.4.3 permits a user agent to turn a POST into a GET on a 302 for historical reasons; §15.4.8 defines 307 precisely so that the method and body are replayed unchanged. Same temporariness, different contract — and if your redirect is in the middle of a form submission, that contract is your request body.
303303 (See Other, §15.4.4) says the answer to your request lives at another URI and the follow-up is a GET. That is what post-redirect-get actually means, and it is unambiguous where 302 only permits the same behaviour. If you are redirecting after a successful POST, 303 states the intent instead of relying on a historical allowance.
301The user-visible difference is permanence; the operational one is caching. 302 is not in RFC 9110 §15.1's list of heuristically cacheable status codes, so without an explicit `Cache-Control` it is never stored and you can change the destination on the next deploy. A 301 is stored, and there is no way to purge it from a visitor's browser. Iterate on 302, promote when you are certain.
401An API that answers an expired session with a 302 to a login page turns every authentication failure into a 200 full of HTML, because the browser follows the redirect before your code sees anything. 401 with a JSON body — and, per RFC 9110 §15.5.2, a `WWW-Authenticate` header — keeps the failure legible to the client that has to handle it.
Reason phrase
Found
Class
3xx redirection
Defined by
RFC 9110
Standard
IETF standard

Tools on this site

All of these run in your browser — nothing is uploaded.

Related status codes

Codes people usually end up reading in the same session as 302.

Frequently asked questions

How do I find out which server produced this status code?

Read the response headers rather than the page. curl -sS -o /dev/null -D - https://example.com/path prints them without the body, and server, via and cf-ray between them name the layer that answered. A cf-ray value means Cloudflare handled the response and is the id their support will ask for. To take the edge out of the picture entirely, repeat the request with --resolve example.com:443:203.0.113.10, which connects to the origin address you name while still sending the original host name and SNI — if the answer changes, the edge and the origin disagree.

Does the reason phrase after the number matter?

No, and you should never branch on it. RFC 9112 tells clients to ignore the reason phrase, servers are free to change it, and HTTP/2 and HTTP/3 do not carry one at all — so 404 Not Found over HTTP/1.1 arrives as a bare 404 over HTTP/2. The phrases on this site are the registered ones because they are what people search for and what appears in an HTTP/1.1 log, not because any software depends on them.

Should I retry a request that failed with this code?

Retry 5xx and 429; do not retry 4xx, because nothing about the request will be different next time. If the response carries Retry-After, honour it — RFC 9110 defines it for exactly this, and it may be either a number of seconds or a date. Otherwise use exponential backoff with jitter and a hard cap, and only for idempotent methods: a retried POST can charge a card twice. A retry storm against a server that is already failing is how a brief incident becomes a long one.

Can I make my server return a different status code?

Yes, and the only rule that matters is that the code has to be true. Anything that reads your responses automatically — search engines, monitoring, caches, client retry logic — makes decisions from the number and never from the page. Serving an error page with 200 hides the failure from your own alerting; serving 200 for a missing page gets the error indexed as content; returning 500 for a request that was simply malformed sends whoever is on call to the wrong half of the stack.

Still stuck on 302? browse every status code in the reference — or go back up to the block written for your side of the stack.