307 vs 308 redirects explained

·4 min read

A 307 redirect is temporary and a 308 redirect is permanent, and both forbid the client from changing the request method, so a POST stays a POST. That is the only thing separating them from 302 and 301. Google treats a 307 like a 302 and a 308 like a 301, so pick between them by asking whether the move is permanent.

If a 307 showed up in Chrome DevTools that your server never sent, skip to the HSTS section. That one is the browser talking to itself.

307 vs 308 redirect at a glance

307 Temporary Redirect 308 Permanent Redirect
Move is Temporary Permanent
Method and body Kept Kept
Closest older code 302 301
Browser caching Only with explicit cache headers Cacheable by default
Google treats it as A 302, weak signal A 301, strong signal
Typical source Next.js redirect(), HSTS upgrades in Chrome Next.js and Vercel permanent redirects

The method rule comes from RFC 9110, the HTTP specification. Its notes on 301 and 302 say a client may switch POST to GET "for historical reasons" and point to 308 and 307 when that is unwanted. 308 first appeared in RFC 7538 in 2014.

RFC 9110 also makes 308 cacheable by default, so a browser can remember it and skip your server next time. Test a new rule as a 307, confirm the target, then make it a 308. For choosing permanent or temporary in general, read the 301 vs 302 guide.

What a 307 redirect means in practice

A 307 says the resource sits at another URL for now, keep using the original, and resend the exact same request there. For a page fetched with GET, a 307 and a 302 behave identically. The difference shows up on form posts and API calls.

Say your checkout form posts to /cart/submit and you move the handler to /api/checkout. After a 302, a browser may follow with a GET and no body, and the handler sees an empty request. After a 307, it repeats the POST with the same body. Use 307 or 308 for any endpoint that receives POST, PUT or DELETE.

Why Chrome shows a 307 internal redirect

Chrome shows "307 Internal Redirect" when HSTS made it upgrade an http:// URL to https:// before sending anything. Your server never sent that 307.

Once your site sends a Strict-Transport-Security header over HTTPS, the browser records the host, and RFC 6797 requires it to replace http with https on every later load. Chromium does this with a synthetic redirect. Its URL request code picks 307 so POST requests survive the upgrade, and its redirect helper adds a Non-Authoritative-Reason: HSTS header. That header tells you it is fake.

To see what the server really sends, ask a client with no HSTS memory. curl applies HSTS only when you pass --hsts with a cache file, so a plain request shows the server's own answer:

curl -sI http://example.com

If that returns a 200 over plain HTTP instead of a redirect, fix the server. First-time visitors and crawlers get no HSTS upgrade.

How Google treats 307 and 308 redirects

Google treats a 308 exactly like a 301 and a 307 exactly like a 302. Its HTTP status code reference calls a permanent redirect a strong signal that the target should be canonical and a temporary one a weak signal.

  1. A 308 is safe for SEO. If your framework sends 308 for permanent moves, you don't need to switch to 301.
  2. A 307 on a permanent move is the same mistake as a 302. Google keeps the old URL in results longer than you want. Check migrations, HTTPS rules and www rules for stray 307s. The browser's HSTS 307 never reaches Googlebot, so it is not this problem.

Google's crawlers follow up to 10 hops, and 307s and 308s count like any other hop in a redirect chain.

Which frameworks send 307 and 308 by default

Next.js and Vercel send 307 and 308 by default, which is why many site owners meet these codes without choosing them.

  • In next.config.js redirects, permanent: true sends a 308 and permanent: false a 307, per the Next.js redirects docs. A statusCode field replaces permanent when an old client needs a 301.
  • The App Router's redirect() sends a 307 and permanentRedirect() a 308. Server Action form posts without JavaScript get a 303.
  • In vercel.json, permanent defaults to true, so a redirect with no flag returns a 308, per Vercel's configuration reference.

The common slip is calling redirect() in a page for a URL that moved for good. That sends a 307. Use permanentRedirect() or a config redirect with permanent: true.

Check which redirect your server really sends

Our redirects, headers and host checker follows a URL hop by hop, up to 10 redirects, and lists each hop's status code, whether it is permanent or temporary, its Location target and its response time. It counts 301 and 308 as permanent and 302, 303 and 307 as temporary. It flags temporary redirects that change host, loops, chains, HTTPS falling back to HTTP and a final HTTPS page with no HSTS header. It requests each hop itself, so you see the server's real answer, never a browser's HSTS upgrade.

The same run checks whether the http, https, www and bare versions of your domain all end on one HTTPS origin. A run costs 10 credits. It follows HTTP redirects only, so a meta refresh or JavaScript redirect after the first page is not traced, and it is not a security audit.

Keep reading