X Xerobit

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...

Mian Ali Khalid · · 4 min read
Use the tool
HTTP Status Codes
Full HTTP status code reference with explanations and when to use each.
Open HTTP Status Codes →

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 Location URL
  • 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 POST to GET when following the redirect (historical behavior)

Use for:

  • HTTP → HTTPS migration
  • www to 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 POST to GET (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:

CodeTypeCachesPreserves MethodUse for
301PermanentYesNo (GET)Legacy permanent redirect
302TemporaryNoNo (GET)Legacy temporary redirect
307TemporaryNoYesTemporary, preserving POST
308PermanentYesYesPermanent, 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 posts

Related tool

HTTP Status Codes

Full HTTP status code reference with explanations and when to use each.

Written by Mian Ali Khalid. Part of the Dev Productivity pillar.