# HTTP 302 Found: Temporary Redirect

> Learn what 302 redirect means, when to use temporary vs permanent redirects, and how 302 differs from 301, 307, and 308.

Source: https://howhttpworks.com/status-codes/302
Last reviewed: 2026-10-05

> **TL;DR:** 302 Found means the requested URL temporarily redirects to another URL. Follow `Location`; if the redirect is unexpected or loops, inspect the first response and the rule that sends it. Use 307 when the request method and body must survive.

## What it means

The HTTP exchanges below are illustrative. A request to `/promo` receives a temporary destination in `Location`.

```http
GET /promo HTTP/1.1
Host: example.com

HTTP/1.1 302 Found
Location: /current-sale
```

302 is not heuristically cacheable. Explicit freshness such as `Cache-Control: max-age=600` can make a GET redirect reusable for ten minutes ([RFC 9111 section 4.2.2](https://www.rfc-editor.org/rfc/rfc9111.html#section-4.2.2)). Express's `res.redirect('/x')` defaults to 302; pass a status explicitly for a permanent move.

## What you see in your client

An HTTP 302 is a redirect response. Automatic following can hide it behind the destination's status, so inspect the first response when your browser and script appear to disagree.

- **Axios:** in Node's HTTP adapter, `maxRedirects: 0` exposes the redirect. With the default success predicate, it rejects with `AxiosError: Request failed with status code 302`. Accept the code in `validateStatus` to inspect `response.headers.location`. The behavior comes from the [HTTP adapter](https://github.com/axios/axios/blob/v1.x/lib/adapters/http.js), [defaults](https://github.com/axios/axios/blob/v1.x/lib/defaults/index.js) and [error construction](https://github.com/axios/axios/blob/v1.x/lib/core/settle.js).
- **Browser fetch:** follows redirects by default and returns the final response. With `redirect: 'manual'`, a browser returns `type: 'opaqueredirect'`, `status: 0` and `ok: false`, with hidden headers. A directly exposed 302 would also have `ok: false`, since only 200–299 passes. Use the Network panel or curl for the actual `Location`; these rules are in the [Fetch Standard](https://fetch.spec.whatwg.org/#concept-request-redirect-mode).
- **Python requests:** `requests.get(url, allow_redirects=False)` exposes 302; `raise_for_status()` returns normally and `response.ok` is `True`. Inspect `response.headers['Location']`. Its [error guard](https://requests.readthedocs.io/en/latest/_modules/requests/models/#Response.raise_for_status) raises for 4xx/5xx, while [GET follows by default](https://github.com/psf/requests/blob/main/src/requests/sessions.py).
- **curl `-f`:** accepts 302; `-L` enables following. Without `-L`, inspect the headers using `-i`. The [manual](https://curl.se/docs/manpage.html#-f) reserves failure code 22 for HTTP responses of 400 or greater.
- **.NET:** set `HttpClientHandler.AllowAutoRedirect = false` to inspect the first response. Calling `EnsureSuccessStatusCode()` then throws `HttpRequestException`; with English resources and reason phrase `Found`, the message is `Response status code does not indicate success: 302 (Found).` See the [redirect setting](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclienthandler.allowautoredirect), [guard](https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.Http/src/System/Net/Http/HttpResponseMessage.cs) and [message resources](https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.Http/src/Resources/Strings.resx).
- **Spring:** [`WebClient.retrieve()`](https://docs.spring.io/spring-framework/reference/web/webflux-webclient/client-retrieve.html) uses `WebClientResponseException` for 4xx/5xx by default. [`RestTemplate`'s default handler](https://github.com/spring-projects/spring-framework/blob/main/spring-web/src/main/java/org/springframework/web/client/DefaultResponseErrorHandler.java) likewise treats those ranges as errors; a 302 alone produces neither that exception nor `HttpClientErrorException`.

## Which temporary redirect?

| Situation                                         | Use | Why                                                             |
| ------------------------------------------------- | --- | --------------------------------------------------------------- |
| After a form POST, send the user to a result page | 303 | Always becomes GET; the precise code for Post/Redirect/Get      |
| Temporary move and the method/body must survive   | 307 | POST stays POST                                                 |
| Plain GET page redirected temporarily             | 302 | Works everywhere; method change is harmless for GET             |

RFC 9110 allows POST to become GET on 302. The [Fetch redirect algorithm](https://fetch.spec.whatwg.org/#http-redirect-fetch) makes that change for browser requests; 307 preserves the method and body. See [302 vs 307](https://howhttpworks.com/compare/302-vs-307).

## Implementation

### Express

```javascript
// Login redirect: encode user input and keep the target on-site
app.get('/dashboard', (req, res, next) => {
  if (!req.user) {
    return res.redirect(302, `/login?returnTo=${encodeURIComponent(req.originalUrl)}`)
  }
  next()
})

// After login: validate returnTo before redirecting (open-redirect defense)
app.post('/login', (req, res) => {
  const target = String(req.query.returnTo || '/')
  const safe = target.startsWith('/') && !target.startsWith('//') && !target.startsWith('/\\')
  res.redirect(303, safe ? target : '/')
})
```

An unvalidated `returnTo` lets an attacker craft `https://yoursite.com/login?returnTo=https://evil.example`, which turns your domain into a phishing launchpad. Allow only same-site relative paths or an explicit allowlist of hosts.

### nginx

```nginx
location = /promo {
    return 302 /current-sale;
}
```

## Common causes by stack

**Express:** [`res.redirect('/login')`](https://expressjs.com/en/5x/api/response/#res.redirect) defaults to 302. Check authentication middleware when an API suddenly returns a login page; use an API's documented authentication response instead of sending its client through an HTML login flow.

**Django:** [`redirect('/login/')`](https://docs.djangoproject.com/en/5.2/topics/http/shortcuts/#redirect) defaults to 302. Check the view or authentication wrapper that chooses the target. Use `permanent=True` for a permanent move; use `preserve_request=True` when the method/body must remain intact, which selects 307 for a temporary redirect.

**Laravel:** `redirect()->to('/dashboard')` uses 302 by default; the [`Redirector` source](https://github.com/laravel/framework/blob/12.x/src/Illuminate/Routing/Redirector.php) declares `$status = 302`. Trace that response call when a successful form POST appears as “302 Found” in the Network panel, then inspect the following GET.

**Rails:** [`redirect_to`](https://api.rubyonrails.org/classes/ActionController/Redirecting.html) defaults to 302. Set `status: :see_other` after a form mutation when the destination is a result page, and inspect the session cookie if the destination sends the user straight back to login.

**nginx / Apache:** nginx's [`return 302 /current-sale;`](https://nginx.org/en/docs/http/ngx_http_rewrite_module.html#return) explicitly emits the temporary redirect. Apache's [`Redirect /promo /current-sale`](https://httpd.apache.org/docs/2.4/mod/mod_alias.html#redirect) uses 302 when the status is omitted. Inspect the loaded virtual host or location for a matching rule before changing application code.

**AWS ALB:** a listener `redirect` action with `StatusCode: HTTP_302` returns 302 before the request reaches a target. Check the matching [listener rule](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/rule-action-types.html#redirect-actions) and its protocol, host, port and path substitutions; at least one must change to avoid a loop.

**Kubernetes ingress-nginx:** `nginx.ingress.kubernetes.io/temporal-redirect` returns 302 instead of forwarding to the upstream. Inspect that [Ingress annotation](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#temporal-redirect) when the application's logs show no request.

FastAPI's Starlette [`RedirectResponse`](https://github.com/encode/starlette/blob/master/starlette/responses.py) defaults to 307, so a FastAPI 302 requires an explicit status or another layer's redirect rule. Inspect `RedirectResponse(url, status_code=302)` before attributing the response to the framework's default.

## SEO behavior

Google [follows a 302](https://developers.google.com/search/docs/crawling-indexing/301-redirects) but does not use that temporary redirect to select the destination as canonical. Other canonicalization signals can affect which URL appears in search. Use [301](https://howhttpworks.com/status-codes/301) or [308](https://howhttpworks.com/status-codes/308) for a permanent move, and retain 302 for a destination you expect to revert.

## Debugging

```bash
curl -sI https://example.com/promo
curl -sIL -o /dev/null -w '%{http_code} -> %{url_effective}\n' https://example.com/promo
# See what a POST turns into (curl -L switches POST to GET on 301/302/303 unless --post302)
curl -si -L -d 'a=1' https://example.com/form
```

Chrome reports [`ERR_TOO_MANY_REDIRECTS`](https://support.google.com/chrome/answer/95669) when a chain loops. Inspect cookies during a repeated dashboard-to-login bounce; for a scheme loop, compare the edge and origin HTTPS rules. Cloudflare documents [loops caused by Flexible mode and origin HTTPS redirects](https://developers.cloudflare.com/ssl/troubleshooting/too-many-redirects/). Trace the chain with the [redirect audit tool](https://howhttpworks.com/tools/redirect-audit).

### Inspect a redirect without following it

For an API call, use `curl -si` with the actual request method and body, then read `Location` before enabling `-L`. A HEAD probe with `-I` can exercise a different route from your POST. Repeat the request with `-L -v` to inspect the outgoing methods in a test environment.

In the command above, `-d` selects POST. Adding `-X POST` would force that method string on subsequent requests with `-L`, masking the normal POST-to-GET change. Use `--post302` only when deliberately testing curl's method-preserving behavior; the [curl manual](https://curl.se/docs/manpage.html#-L) distinguishes it from the default.

## Post/Redirect/Get

PRG makes a result-page reload repeat a GET instead of submitting the original POST again. The server handles the POST, then redirects the browser to a GET page; reloading that page repeats only the GET. Fetch changes POST to GET on 302, but 303 states the intended retrieval explicitly ([RFC 9110 section 15.4.4](https://www.rfc-editor.org/rfc/rfc9110.html#section-15.4.4)).

```http
POST /posts HTTP/1.1
Host: example.com
Content-Length: 0

HTTP/1.1 303 See Other
Location: /posts/123
```

## Related

- [301 Moved Permanently](https://howhttpworks.com/status-codes/301)
- [303 See Other](https://howhttpworks.com/status-codes/303)
- [307 Temporary Redirect](https://howhttpworks.com/status-codes/307)
- [Location header](https://howhttpworks.com/headers/location)
