HTTP 301 vs 302 Redirect — Permanent vs Temporary Redirects Explained
Understand when to use 301 (permanent) vs 302 (temporary) redirects, how they affect SEO page rank transfer, browser caching, and which status code to use for specific...
301 tells browsers and search engines “this URL moved forever — update your bookmarks and links.” 302 tells them “temporarily using a different URL — keep the original.” The difference matters for SEO and browser caching.
Look up HTTP status codes with the HTTP Status Codes reference.
301 Permanent Redirect
HTTP/1.1 301 Moved Permanently
Location: https://new-url.example.com/page
Cache-Control: max-age=31536000
What it signals:
- The resource has permanently moved to the
LocationURL - Browsers cache the redirect — future requests go directly to the new URL
- Search engines transfer ~90-99% of PageRank to the new URL
- Browsers change
POSTtoGETwhen following the redirect (historical behavior)
Use for:
- HTTP → HTTPS migration
wwwto non-www(or vice versa)- Domain migration (old-domain.com → new-domain.com)
- Permanent URL restructuring (
/blog/post-1→/articles/post-1)
302 Found (Temporary Redirect)
HTTP/1.1 302 Found
Location: https://temporary-url.example.com/page
What it signals:
- Use the original URL for future requests — this redirect is temporary
- Browsers do NOT cache 302 by default
- Search engines do NOT transfer PageRank to the new URL
- Browsers change
POSTtoGET(same issue as 301)
Use for:
- Maintenance page redirects
- A/B testing (send some users to variant)
- Unauthenticated users redirected to login (redirect back after auth)
- Feature flags routing users to different versions
307 and 308: the modern alternatives
301/302 both change POST to GET — a historical bug. 307/308 preserve the HTTP method:
| Code | Type | Caches | Preserves Method | Use for |
|---|---|---|---|---|
| 301 | Permanent | Yes | No (GET) | Legacy permanent redirect |
| 302 | Temporary | No | No (GET) | Legacy temporary redirect |
| 307 | Temporary | No | Yes | Temporary, preserving POST |
| 308 | Permanent | Yes | Yes | Permanent, preserving POST |
POST /submit → 301 → GET /new-submit (form data lost!)
POST /submit → 308 → POST /new-submit (correct behavior)
Recommendation: Prefer 308 over 301 for API endpoint migrations where methods matter. Prefer 301 for web page redirects (where changing POST→GET is often acceptable or desired).
Implementation examples
Express/Node.js:
import express from 'express';
const app = express();
// 301 Permanent (default for .redirect):
app.get('/old-page', (req, res) => {
res.redirect(301, '/new-page');
});
// 302 Temporary (e.g., login redirect):
app.get('/dashboard', (req, res) => {
if (!req.session.userId) {
return res.redirect(302, '/login?return=/dashboard');
}
res.render('dashboard');
});
// 307 Temporary, preserving method (API):
app.all('/api/v1/users', (req, res) => {
res.redirect(307, '/api/v2/users');
});
nginx:
# 301: HTTP to HTTPS
server {
listen 80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
# 301: www to non-www
server {
listen 443 ssl;
server_name www.example.com;
return 301 https://example.com$request_uri;
}
# 302: maintenance mode
location / {
return 302 /maintenance.html;
}
Next.js (next.config.js):
module.exports = {
async redirects() {
return [
{
source: '/old-blog/:slug',
destination: '/articles/:slug',
permanent: true, // true = 308, false = 307
},
{
source: '/sale',
destination: '/deals',
permanent: false, // temporary promotional redirect
},
];
},
};
SEO: how PageRank transfers
Old page: 100 PageRank units
301 redirect → new page gets ~95-99 units (Google updated guidance, 2016)
302 redirect → new page gets 0 units (PageRank stays on old URL)
No redirect → PageRank stays on old URL (404 loses it eventually)
When migrating a website, use 301s (or 308s) for all pages to preserve SEO equity. After 6-12 months, Google typically passes nearly full link equity through 301s.
Redirect chains: avoid at all costs
User → /old → 301 → /interim → 301 → /final
Each hop loses a small % of PageRank and adds latency.
Solve: /old should redirect directly to /final.
// Audit redirect chains with curl:
// curl -L -I https://example.com/old-page
// Shows all intermediate responses
// Node.js chain detector:
async function checkRedirectChain(url, hops = []) {
const res = await fetch(url, { redirect: 'manual' });
hops.push({ url, status: res.status });
if ([301, 302, 307, 308].includes(res.status)) {
const next = res.headers.get('location');
if (next && hops.length < 10) {
return checkRedirectChain(
new URL(next, url).toString(),
hops
);
}
}
return hops;
}
Related tools
- HTTP Status Codes — reference for all HTTP codes
- URL Encoder — encode redirect URLs
- URL Canonicalization — normalize URLs before redirecting
Related posts
- When to Use 422 vs 400 (and Other HTTP Status Code Debates) — 400 means the request is malformed. 422 means it's valid but semantically wrong.…
- HTTP 404 Not Found — What It Means and How to Fix It — HTTP 404 means the server understood the request but can't find the resource. It…
- HTTP 500 Internal Server Error — What It Means and How to Debug It — HTTP 500 means the server encountered an unexpected error it couldn't handle. Un…
- HTTP Cache Headers — Cache-Control, ETag, and Last-Modified Explained — Master HTTP caching headers: Cache-Control directives (max-age, no-cache, immuta…
- URL Canonicalization — Normalize URLs for APIs, Caching, and SEO — URL canonicalization normalizes URLs to a consistent form, removing redundant pa…
Related tool
Full HTTP status code reference with explanations and when to use each.
Written by Mian Ali Khalid. Part of the Dev Productivity pillar.