Understanding HTTP 302 Found: Temporary Redirects, the POST-to-GET Trap, and When to Use 307 Instead
302 Found is the redirect you get when you don't ask for one by name. It's the default in Express, PHP, Rails, and most other frameworks — which means it's the most-used redirect on the web, and also the most accidentally used. Two things about it routinely surprise developers: it can silently change a POST request into a GET, and using it for a permanent move quietly throws away your SEO. Even its name is odd — "Found" doesn't say "temporary" anywhere. Let's unpack what 302 actually promises, how it compares to 301, 303, 307, and 308, and when modern code should reach for 307 instead.
What Is a 302?
A 302 tells the client that the resource it asked for is temporarily living at a different URL, provided in the Location header. Crucially, the move isn't final — the client should keep using the original URL next time.
The 302 (Found) status code indicates that the target resource resides temporarily under a different URI. Since the redirection might be altered on occasion, the client ought to continue to use the target URI for future requests. — RFC 9110, Section 15.4.3
In plain English: "What you want is over there for now. Don't update your bookmarks." The redirect might point somewhere else tomorrow, or disappear entirely, so nothing — not browsers, not search engines — should treat the new URL as the resource's real home.
A minimal 302 response looks like this:
HTTP/1.1 302 Found
Location: /temporary-pageWhy Is It Called "Found"?
The name is a fossil. In HTTP/1.0, code 302 was called "Moved Temporarily" — a much clearer name. But early browsers implemented it wrong: when a POST request got a 302, they followed the redirect with a GET instead of re-sending the POST. The behavior was so widespread that HTTP/1.1 gave up fighting it. The spec renamed 302 to "Found," blessed the sloppy method-changing behavior as officially tolerated, and introduced two new codes to say precisely what 302 no longer could:
- 303 See Other — "definitely switch to GET" (the redirect-after-POST pattern)
- 307 Temporary Redirect — "definitely keep the original method"
So 302 survives mostly for legacy compatibility. It works fine for everyday GET-to-GET redirects, but the moment a non-GET request is involved, its behavior is officially ambiguous — which is why the RFC carries this warning:
Note: For historical reasons, a user agent MAY change the request method from POST to GET for the subsequent request. — RFC 9110, Section 15.4.3
The POST-to-GET Trap
That "MAY" is the sharpest edge on this status code. Say your API moves temporarily and you 302 the old endpoint to the new one:
POST /api/orders HTTP/1.1
Content-Type: application/json
{"item": "coffee", "qty": 2}HTTP/1.1 302 Found
Location: /api/v2/ordersIn practice, virtually every browser follows that redirect with GET /api/v2/orders — no body, wrong method. Your order never gets created, and depending on how the new endpoint handles a bodyless GET, you might get a confusing 405, a 400, or worst of all a 200 that did nothing. The client-side fetch API does the same thing, so this bites single-page apps too.
The heuristic to remember:
- 302 = "temporary, and the method might change" — safe for GET navigation, risky for everything else
- 307 = "temporary, and the method is guaranteed to survive" — safe for APIs, forms, uploads
- 303 = "temporary, and the method must become GET" — exactly what you want after a successful form POST
If you're redirecting anything other than a plain GET and you don't explicitly want the method to change, use 307. That's not just pedantry — it's why Next.js made 307 its framework-wide default (more on that below).
302 vs 301, 303, 307, and 308
The 3xx family splits along two axes: is the move permanent? and is the HTTP method preserved? 302 sits in the temporary/ambiguous corner:
| Code | Meaning | Permanent? | Method preserved? | Use when |
|---|---|---|---|---|
| 301 Moved Permanently | Gone for good | Yes | No (may become GET) | Permanent URL change, SEO migration |
| 302 Found | Temporarily elsewhere | No | No (may become GET) | Temporary redirect of GET traffic; legacy compatibility |
| 303 See Other | Result is over there — GET it | No | No (always becomes GET) | Redirect-after-POST, pointing to an operation's result |
| 307 Temporary Redirect | Temporarily elsewhere | No | Yes | Temporary redirect where POST must stay POST |
| 308 Permanent Redirect | Gone for good | Yes | Yes | Permanent move of API endpoints, form targets |
Two comparisons matter most in practice:
- 302 vs 301 — temporary vs permanent. This is an SEO decision as much as a technical one: a 301 tells search engines to transfer the old URL's ranking signals to the new one; a 302 tells them to keep the old URL indexed and treat the detour as noise. Pick wrong in either direction and rankings suffer.
- 302 vs 307 — same temporariness, different method guarantees. 307 is simply the better-specified modern 302. New code that needs a temporary redirect and cares about non-GET requests should default to 307.
You can compare 301 vs 302 and compare 302 vs 307 side by side, including interactive decision trees.
When to Use a 302
Legitimate 302 use cases are all genuinely short-lived situations where the original URL will come back:
- Temporary maintenance redirects —
/dashboardpoints at/maintenancefor an hour while you migrate a database. (If the whole site is down, prefer a 503 so crawlers know to come back later rather than indexing your maintenance page.) - A/B testing and experiments — a fraction of traffic to
/pricingis temporarily bounced to/pricing-b. Google explicitly recommends 302 here, because you don't want the variant URL replacing the original in search results. - Short-term campaigns —
/dealpoints at this week's promotion page and will point somewhere else next week. - Geo or device detours — sending users to a country-specific page while keeping the canonical URL stable.
- Login flows for browser navigation — bouncing an unauthenticated user from a protected page to
/loginis classically a 302. (For API endpoints, don't redirect at all — return a 401 and let the client decide.)
The common thread: in every case the original URL remains the real address, and the redirect is expected to change or disappear. If you catch yourself writing a 302 for a URL that's never coming back — a renamed page, an HTTPS migration, a domain move — stop and use a 301 or 308 instead.
Returning 302 Correctly
Express / Node.js
Express makes 302 the default, which is exactly how accidental 302s happen. Be explicit either way:
// This is a 302 — the default when you don't pass a status
app.get("/deal", (req, res) => {
res.redirect("/promotions/summer-2026");
});
// Say what you mean: explicit status first
app.get("/dashboard", (req, res) => {
if (maintenanceMode) {
return res.redirect(302, "/maintenance");
}
res.render("dashboard");
});
// Temporary redirect of a POST endpoint? Use 307 so the method survives
app.post("/api/orders", (req, res) => {
res.redirect(307, "/api/v2/orders");
});Next.js App Router
Here's the interesting part: Next.js deliberately avoids 302. The App Router's redirect() function returns a 307 (or a 303 inside Server Actions), and next.config.ts redirects use 307 for permanent: false and 308 for permanent: true — precisely because of the POST-to-GET ambiguity described above.
// app/dashboard/page.tsx — this sends a 307, not a 302
import { redirect } from "next/navigation";
export default async function Dashboard() {
const session = await getSession();
if (!session) {
redirect("/login");
}
// ...
}// next.config.ts — permanent: false emits a 307
const nextConfig = {
async redirects() {
return [
{
source: "/deal",
destination: "/promotions/summer-2026",
permanent: false, // 307 Temporary Redirect
},
];
},
};
export default nextConfig;If you specifically need a literal 302 — say, an external system requires it — you can set the status yourself in a Route Handler or proxy with NextResponse.redirect:
// app/go/route.ts
import { NextResponse } from "next/server";
export function GET(request: Request) {
return NextResponse.redirect(new URL("/temporary-page", request.url), 302);
}For nearly all Next.js code, though, the framework's 307/308 defaults are the right call — treat them as "302/301 with the method bug fixed."
NGINX
# Explicit temporary redirect
location /deal {
return 302 /promotions/summer-2026;
}
# The `redirect` flag on rewrite is also a 302 (vs `permanent`, which is 301)
rewrite ^/old-campaign/(.*)$ /campaign/$1 redirect;Apache (.htaccess)
# Redirect defaults to 302 when no status is given — be explicit anyway
Redirect 302 /deal /promotions/summer-2026
# mod_rewrite: R alone means R=302
RewriteEngine On
RewriteRule ^deal$ /promotions/summer-2026 [R=302,L]What the response should look like
Keep 302 responses lean: the Location header does the work. A small HTML body is a courtesy fallback for ancient clients, and browsers show it only if they can't follow the redirect.
HTTP/1.1 302 Found
Location: https://example.com/promotions/summer-2026
Content-Type: text/html
<html><body>Temporarily moved <a href="https://example.com/promotions/summer-2026">here</a>.</body></html>Note what's not there: unlike a 301, a 302 is not cacheable by default. Browsers will re-request the original URL every time, which is exactly what "temporary" should mean — and it's also your safety net, because a mistaken 302 is trivially reversible in a way a browser-cached 301 is not.
302 and SEO
The SEO story is where 301 and 302 diverge completely, and it follows directly from "temporary":
- Indexing — the original URL stays in Google's index. Ranking signals are not passed to the destination, because the destination is understood to be a stand-in, not a successor.
- Crawl behavior — crawlers follow the redirect to see what's there, but they keep re-crawling the original URL, expecting it to start answering with content again.
- Canonical signals — a 302 is a weak canonical hint. Here's the twist: if you leave a 302 in place for months, Google may eventually decide you really meant "permanent," treat it like a 301, and index the destination instead. Long-lived 302s don't stay 302s in Google's eyes.
- The practical rule — use 302 only for genuinely temporary situations (A/B tests, maintenance, short campaigns). A long-running 302 gives you the worst of both worlds: no link-equity transfer, plus unpredictable canonicalization.
If you've inherited a site, it's worth auditing for 302s that should be 301s — it's one of the most common and quietly expensive SEO misconfigurations, usually caused by a framework's redirect default rather than a deliberate choice. A quick check:
# See which redirect status a URL actually returns
curl -sI https://example.com/old-page | grep -E "HTTP/|Location:"Common Pitfalls
- Using 302 for a permanent move. The framework default strikes again:
res.redirect("/new-home")in Express is a 302, so your carefully planned migration passes zero link equity. If the old URL is never coming back, use 301 (or 308 for non-GET endpoints). - 302-redirecting POST requests and losing the body. The redirect "works" in testing with GET, then production POSTs silently arrive as bodyless GETs. Use 307 when the method must survive.
- Using 302 for redirect-after-POST when you mean 303. After a successful form submission, redirecting to a confirmation page with 302 usually works — browsers happen to switch to GET. But 303 states that intent explicitly and behaves identically in every client. Say what you mean.
- Leaving "temporary" redirects in place for years. Google eventually reinterprets a long-lived 302 as a 301, at a time of its choosing. If the redirect has become permanent in practice, make it a 301 deliberately so you control when the index updates.
- Redirect chains through mixed codes.
/a → 302 → /b → 301 → /cmakes the overall signal ambiguous and burns crawl budget on every hop. Point redirects directly at the final destination, and keep the code consistent with your intent. - Adding cache headers to a 302. A 302 with
Cache-Control: max-age=604800is a contradiction — you've told the client the move is temporary but to stop checking for a week. If you want the redirect cached, it isn't temporary; use a 301/308. - Redirecting API clients at all when you could answer directly. Browsers follow redirects invisibly, but every 302 in an API adds a round trip and a class of client bugs (dropped headers, method changes, redirect limits). For APIs, prefer stable URLs — and when endpoints do move, prefer 307/308 with a deprecation plan.
Wrapping Up
302 is the web's default detour sign — fine at what it does, dangerous when it's chosen by omission. The rules of thumb:
- 302 means temporary: the original URL stays canonical, keeps getting crawled, and keeps its SEO value — nothing transfers to the destination
- 302 may silently turn POST into GET; use 307 when the method must be preserved, 303 when you explicitly want the switch
- Permanent moves get 301/308 — never a 302, no matter what your framework defaults to
- Long-lived 302s get reinterpreted as 301s by search engines; if a redirect has become permanent, promote it deliberately
- Modern frameworks are moving on: Next.js emits 307/308 instead of 302/301 by default, and that's the right instinct for new code
For more, see our pages on 302 Found, 303 See Other, and 307 Temporary Redirect. The permanent side of the family is covered in our posts on understanding 301 redirects and understanding 308 Permanent Redirect, and you can compare 301 vs 302 or 302 vs 307 side by side.