X Xerobit

HTTP Redirect Status Codes — 301, 302, 307, 308 Explained

HTTP 3xx redirect codes tell clients to look elsewhere for a resource. Here's when to use 301 vs 302 vs 307 vs 308, how redirects affect SEO, and how to implement redirects in...

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 →

HTTP 3xx codes tell browsers and crawlers that a resource has moved. The code you choose affects whether the redirect is permanent or temporary, and whether the HTTP method (GET/POST) is preserved.

Use the HTTP Status Codes Reference to look up any HTTP status code.

The 3xx redirect codes

CodeNamePermanent?Method preserved?Use case
301Moved PermanentlyYesNo (GET on follow)Permanent URL changes
302FoundNoNo (GET on follow)Temporary redirects
303See OtherNoAlways GETPOST-redirect-GET pattern
307Temporary RedirectNoYesTemporary, keep POST
308Permanent RedirectYesYesPermanent, keep POST

301 Moved Permanently

The resource has permanently moved. Browsers cache it; search engines transfer link equity.

HTTP/1.1 301 Moved Permanently
Location: https://new.example.com/page

Use when:

  • Changing your domain (http://old.comhttps://new.com)
  • Restructuring URLs (/products/123/items/123)
  • Adding HTTPS permanently
  • Moving a page forever

SEO effect: Passes PageRank to the new URL. Google typically follows and indexes the new URL within days.

Caution: Browsers cache 301s aggressively. Testing with 301 can be frustrating — use 302 during testing, then change to 301.

302 Found (Temporary)

Temporary redirect — the original URL will come back. Browsers don’t cache it indefinitely.

HTTP/1.1 302 Found
Location: /maintenance

Use when:

  • Maintenance pages
  • A/B testing
  • Conditional redirects based on login state
  • Promotional pages that will go away

Note: Both 301 and 302 may change POST to GET on the redirected request (browser behavior varies). Use 307/308 if you need to preserve the method.

303 See Other

Always redirects to a GET request. The canonical use case: after a successful POST form submission, redirect to a success page.

POST /checkout
→ 303 See Other
→ GET /order-confirmation

This is the POST-Redirect-GET pattern. Without it, refreshing the page would resubmit the form.

// Express:
app.post('/checkout', async (req, res) => {
  const order = await processOrder(req.body);
  res.redirect(303, `/orders/${order.id}/confirmation`);
});

307 Temporary Redirect

Like 302, but guarantees the HTTP method is preserved:

POST /api/v1/users → 307 → POST /api/v2/users

Use when:

  • API versioning where you need to preserve POST/PUT/PATCH
  • Temporary backend moves while maintaining API compatibility

308 Permanent Redirect

Like 301, but preserves the HTTP method:

POST /api/v1/resource → 308 → POST /api/v2/resource

Use when:

  • Permanently moving an API endpoint while maintaining method

Implementing redirects

Express.js

// 301 permanent:
app.get('/old-path', (req, res) => {
  res.redirect(301, '/new-path');
});

// 302 temporary (default):
app.get('/promo', (req, res) => {
  res.redirect('/current-promotion');
});

// 303 after POST:
app.post('/submit', async (req, res) => {
  await saveData(req.body);
  res.redirect(303, '/success');
});

// Redirect all HTTP to HTTPS:
app.use((req, res, next) => {
  if (req.header('x-forwarded-proto') !== 'https') {
    return res.redirect(301, `https://${req.hostname}${req.url}`);
  }
  next();
});

Nginx

# HTTP to HTTPS:
server {
  listen 80;
  return 301 https://$server_name$request_uri;
}

# Old domain to new:
server {
  server_name old.example.com;
  return 301 https://new.example.com$request_uri;
}

# Specific path redirect:
location /old-path {
  return 301 /new-path;
}

# Trailing slash:
rewrite ^(/path)$ $1/ permanent;

Next.js

// next.config.js
module.exports = {
  async redirects() {
    return [
      {
        source: '/old-page',
        destination: '/new-page',
        permanent: true,   // 308; false = 307
      },
      {
        source: '/blog/:slug',
        destination: '/posts/:slug',
        permanent: true,
      },
    ];
  },
};

Apache .htaccess

# HTTP to HTTPS:
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

# Old URL to new:
Redirect 301 /old-page.html /new-page

# Redirect entire old domain:
RewriteCond %{HTTP_HOST} ^old\.example\.com [NC]
RewriteRule (.*) https://new.example.com/$1 [R=301,L]

SEO and redirect chains

Avoid redirect chains (A → B → C) — they slow down crawling and dilute PageRank:

# BAD: redirect chain
/page → /old-page → /new-page → /final-page

# GOOD: direct redirect
/page → /final-page

Also avoid redirect loops (A → B → A), which cause browsers to show “Too many redirects” errors.


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.