301 vs 302 redirects: which one to use
·7 min read
In the 301 vs 302 choice, use a 301 when a URL has moved for good and a 302 when the original URL will come back. Google reads a 301 as a strong sign that the new URL should replace the old one in search results, and a 302 as a sign to keep showing the old one. If you can't name the day the old URL returns, the move is permanent and you want a 301.
307 and 308 are the strict versions of 302 and 301. Google treats them the same, but they stop the browser from turning a POST into a GET, which is why Next.js uses them by default.
301, 302, 303, 307 and 308 compared
| Code | Name | Permanent or temporary | Can POST become GET? | Google reads it as | Use it for |
|---|---|---|---|---|---|
| 301 | Moved Permanently | Permanent | Yes, browsers may switch it | Strong signal, target replaces source | Pages and sites that moved for good |
| 302 | Found | Temporary | Yes, browsers may switch it | Weak signal, source stays in results | Campaign URLs, A/B tests, login detours |
| 303 | See Other | Temporary | Always becomes GET | Same as 302 | Sending the browser to a results page after a form POST |
| 307 | Temporary Redirect | Temporary | No, method and body are kept | Same as 302 | Temporary moves of form or API endpoints |
| 308 | Permanent Redirect | Permanent | No, method and body are kept | Same as 301 | Permanent moves of form or API endpoints |
The Google column comes from Google's HTTP status code reference, which lists 307 as equivalent to 302 and 308 as equivalent to 301. For an ordinary page that people reach with a GET request, a 301 and a 308 do the same job in search.
301 vs 302: when to use each
Ask one question. Will the old URL ever be the page you want in search results again? If not, use a 301. If it will, use a 302.
Use a 301 or 308 for:
- a page whose URL changed, such as /blog/2019/redirect-guide to /blog/redirects
- two pages merged into one
- HTTP to HTTPS, and www to the bare domain or the reverse
- a domain move or a rebrand
- a discontinued product with a direct replacement
Use a 302 or 307 for:
- a short URL like /sale that points at a different campaign page each season
- an A/B test that sends some visitors to a variant URL, which Google's website testing guidance says should be a 302
- sending a signed-out visitor from /account to /login
Skip the redirect for planned downtime. Google's post on planned site downtime recommends a 503 status with an optional Retry-After header, not a 302 to a maintenance page.
What is an HTTP 302 redirect?
HTTP 302 Found tells the client that the page is at another URL for now and that the original URL stays valid for future requests. The new address travels in the Location header:
HTTP/1.1 302 Found
Location: https://example.com/sale/autumn-2026Plenty of 302s were never a decision. They are a default. PHP's header("Location: ...") and Express's res.redirect() both send a 302 unless you ask for another code. So when an old URL still shows in Google months after a move, check the redirect's status code first.
Permanent vs temporary redirect: how Google treats each
A permanent redirect tells Google the target should be the canonical URL, so the new URL replaces the old one in results. A temporary redirect doesn't, so Google keeps showing the source. Google's redirects documentation spells this out, and its status code reference calls the first a strong signal and the second a weak one.
A few details change what you do in practice.
- A 302 is not a veto. Google says the target can still be indexed if other canonical signals point at it, such as internal links and canonical tags.
- Permanent redirects don't leak link value. Google's site move guide says 301 and other permanent redirects don't cause a loss in PageRank.
- The same guide asks you to keep redirects in place for as long as possible, generally at least a year.
- Don't wait for Google to guess. SEOs often say a 302 left in place long enough gets treated as a 301, but Google's documentation promises only a weak signal and no timeline. If the move is permanent, change the code.
Server-side redirects are the kind Google trusts most. It reads an instant meta refresh as permanent and a delayed one as temporary, and it warns that it may never see a JavaScript redirect if rendering fails.
307 vs 308 and why the request method matters
307 and 308 exist because browsers bent the rules on 301 and 302. RFC 9110, the HTTP specification, lets a user agent change a POST into a GET after a 301 or 302 "for historical reasons", and browsers do. A 307 or 308 forbids that change, so the method and the body arrive intact.
Here is where it bites. Your newsletter form posts to /subscribe. You move the handler to /api/subscribe and add a 301. The browser follows it with a GET and no body, the handler receives an empty request, and signups stop with no error a visitor would notice. A 308 keeps the POST.
303 is the deliberate opposite. After a form POST succeeds, a 303 sends the browser to a confirmation page with a GET, so a refresh doesn't submit the form twice.
How to set up a 301 or 302 redirect
Put redirects in the server or framework config. They run before any page code, and Google handles them more reliably than client-side redirects.
nginx
# Inside the HTTPS server block for example.com
# One page, moved for good
location = /old-pricing {
return 301 https://example.com/pricing;
}
# A campaign URL that changes each season
location = /sale {
return 302 https://example.com/sale/autumn-2026;
}
# HTTP and www go to https://example.com in one hop
server {
listen 80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
server {
listen 443 ssl;
server_name www.example.com;
# ssl_certificate and ssl_certificate_key go here
return 301 https://example.com$request_uri;
}$request_uri keeps the path and query string, so every old URL lands on its HTTPS twin in one hop.
Apache
In .htaccess or the virtual host:
# mod_alias: simple page moves
Redirect 301 /old-pricing https://example.com/pricing
Redirect 302 /sale https://example.com/sale/autumn-2026
# mod_rewrite: HTTP and www go to https://example.com in one hop
RewriteEngine On
RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} ^www\. [NC]
RewriteRule ^ https://example.com%{REQUEST_URI} [R=301,L]Redirect matches by prefix, so /old-pricing/faq also moves, to /pricing/faq. Use RedirectMatch 301 ^/old-pricing$ https://example.com/pricing when you want an exact match. If a CDN or load balancer in front of the server talks to it over plain HTTP, %{HTTPS} is always off at the origin and this rule loops forever, which is one of the usual causes of ERR_TOO_MANY_REDIRECTS.
Next.js
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
async redirects() {
return [
// permanent: true sends a 308
{ source: "/old-pricing", destination: "/pricing", permanent: true },
// permanent: false sends a 307
{ source: "/sale", destination: "/sale/autumn-2026", permanent: false },
// statusCode replaces permanent when a client needs a plain 301
{ source: "/docs/:path*", destination: "/guides/:path*", statusCode: 301 },
];
},
};
export default nextConfig;The Next.js redirects docs explain that permanent: true sends a 308 and permanent: false sends a 307, to keep the request method intact. Google treats those exactly like 301 and 302, so keep the defaults unless an old client needs a 301. Query strings pass through to the destination.
Redirect mistakes that cost rankings
A 302 for an HTTPS or domain migration
This is the expensive one. A temporary redirect gives Google no reason to swap the old URLs for the new ones, so the old domain or the http:// versions can stay in results. Google's site move guide asks for permanent server-side redirects. Check the first hop's status code, not only the page you end up on.
Redirect chains
Chains build up over years of migrations. http://example.com/old goes to https://example.com/old, then to https://www.example.com/old, then to /new. Google's crawlers follow up to 10 hops, but the site move guide says to avoid chaining and redirect to the final destination directly. Point each old rule at today's URL, and update internal links and sitemaps so they skip redirects entirely. After a migration, paste the old URLs into the bulk HTTP status checker to see which ones return 200, redirect or fail.
Redirecting every dead URL to the homepage
It looks tidy and it doesn't work. Google's site move guide warns against sending many old URLs to one irrelevant page such as the home page, because it confuses users and may be treated as a soft 404. Send each URL to its closest real equivalent. If nothing matches, let it return a 404 or 410.
A wrong 301 that browsers remember
RFC 9110 makes 301 and 308 responses cacheable by default, and browsers can keep them for a long time. Once a visitor's browser has cached your mistaken 301, it can go straight to the wrong target without asking your server, even after you fix the rule. Test new rules as a 302 or 307, check them with curl instead of a browser, and switch to a 301 once the target is right:
# One response: the status code and the Location header
curl -sI https://example.com/old-pricing
# Every hop until the final page
curl -sIL https://example.com/old-pricingTrace your redirect chain and host versions
Our redirects, headers and host checker follows a URL hop by hop, up to 10 redirects, and shows each hop's status code, whether it is permanent or temporary, its Location target and how long it took. It flags loops, chains of two or more redirects, redirects with no Location, HTTPS falling back to HTTP, temporary redirects that change host, hops slower than 3 seconds, and a final page that errors or isn't served over HTTPS.
In the same run it requests the http and https versions of your bare domain and www, and reports whether all four end on one HTTPS origin. It also flags response headers such as an X-Robots-Tag noindex, a missing HSTS header or a Content-Security-Policy that blocks scripts or images. Each finding comes with the change to make, and a run costs 10 credits.
It follows HTTP redirects only. If a page answers 200 and then redirects with a meta refresh or JavaScript, the trace stops at that 200. It is not a security audit either.