Blog
3 min read

301 vs 302 Redirects (and 307, 308): Which Should You Use?

301 means moved permanently; 302 means temporarily somewhere else. How each affects browsers, caching and SEO, what 307 and 308 add, setting redirects in Nginx, Caddy, Next.js, Express and Cloudflare, and the mistakes that cause redirect loops and lost rankings.

A redirect tells the browser: "what you asked for is at a different address." The server responds with a 3xx status code and a Location header, and the browser goes there automatically. (HTTP status codes explained)

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

The main question is permanent or temporary?

301: moved permanently

"This page lives at the new address now and forever."

  • Browsers cache it — next time they go straight to the new URL without asking.
  • Search engines transfer ranking signals to the new URL and replace the old one in results.

Use for:

  • http:// → https://
  • www.example.com → example.com (or the reverse) (www vs non-www)
  • an old URL after renaming a page or restructuring the site
  • moving to a new domain

302: found (temporary)

"For now, go here — but the original address is still the real one."

  • Browsers don't cache it long-term.
  • Search engines keep the original URL indexed.

Use for:

  • a temporary maintenance or sale page
  • sending logged-out users to /login
  • A/B tests and geo- or language-based routing

307 and 308: the stricter versions

Historically, browsers changed a POST into a GET when following 301/302. 307 and 308 forbid that — the method and body are kept.

Code Permanent? Keeps method (POST stays POST)?
301 Yes Not guaranteed
302 No Not guaranteed
307 No Yes
308 Yes Yes

For normal page redirects, 301/302 are fine. For API endpoints or form submissions, use 307/308. Search engines treat 308 like 301. Next.js uses 307/308 by default.

Setting redirects

Nginx:

server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://example.com$request_uri;
}

location = /old-pricing { return 301 /pricing; }

Caddy:

www.example.com {
    redir https://example.com{uri} permanent
}

(Caddy redirects HTTP to HTTPS automatically.) (What is Caddy?)

Next.js (next.config.js):

async redirects() {
  return [{ source: '/old-pricing', destination: '/pricing', permanent: true }]
}

Express:

app.get('/old-pricing', (req, res) => res.redirect(301, '/pricing'))

Note Express's res.redirect() defaults to 302.

Cloudflare: Rules → Redirect Rules, without touching your server.

Common mistakes

  • Using 302 for permanent moves — rankings may stay with the old URL. Many frameworks default to 302/307; check.
  • Using 301 for something temporary — browsers cache it, and users keep getting redirected after you undo it. Test with 302, switch to 301 when sure.
  • Redirect chains — http://www → https://www → https:// → /home. Each hop adds delay; redirect straight to the final URL.
  • Redirect loops — A → B → A, causing ERR_TOO_MANY_REDIRECTS. Classic with Cloudflare's Flexible SSL mode plus a server HTTPS redirect. (ERR_TOO_MANY_REDIRECTS)
  • Redirecting everything to the homepage after a redesign — search engines treat that like a 404. Map old pages to their closest new equivalents.
  • Open redirects — redirecting to a URL taken from a query parameter without checking it. (Open redirect vulnerability)

Checking redirects

curl -I http://example.com/old-pricing
curl -sIL http://example.com | grep -iE "^HTTP|^location"

-L follows the chain, showing every hop. (What is curl?)


EasySpawn handles the HTTP-to-HTTPS redirect for you automatically, and your redirects live in your own app or proxy config. See how it works or join the waitlist.

Related: Canonical URLs Explained · ERR_TOO_MANY_REDIRECTS · www vs non-www · HTTP Status Codes Explained

Keep reading