Skip to content

HTTP status code · Redirection (3xx)

301 Moved Permanently

301 Moved Permanently means the resource now lives at the URL in the Location header and every future request should go there. It is the standard way to move a page for good, for example after changing a slug, switching from HTTP to HTTPS or merging www and non-www.

Facts about this status code
Class3xx, Redirection
Defined inRFC 9110 §15.4.2
Cacheable by defaultYes, heuristically cacheable; without Cache-Control, browsers often keep it with no expiry
Safe to retryFollow Location now and send all future requests to the new URL
Relevant headers
  • Location: the new permanent URL; relative references are resolved against the request URL
  • Cache-Control: add a max-age while testing so a wrong redirect does not stick in browsers

What 301 means

RFC 9110, section 15.4.2, says a 301 means the target has been given a new permanent URI and future references ought to use it. Browsers follow the Location header immediately and, because a 301 is heuristically cacheable, Chrome and Firefox usually remember it with no expiry. Visitors can keep being sent to the new URL from their own cache long after you delete the rule on the server.

The spec also keeps a historical quirk: a user agent may change a POST into a GET when following a 301, and every major browser does, dropping the request body on the way. For a form or API endpoint that receives POST, PUT or DELETE, send 308 Permanent Redirect, which keeps the method and body.

Google's documentation calls a 301 a strong signal that the redirect target should be canonical, so the new URL replaces the old one in search results. Chains still cost you: Google's crawlers stop after 10 hops, and each extra hop is another round trip for a visitor on a slow connection.

When to use it

  • A page, product or article moved to a new URL for good.
  • Enforcing one canonical scheme and host: http to https, www to the bare domain or the reverse.
  • Consolidating old URLs after a site migration or a change of URL structure in the CMS.
  • Keep it for GET and HEAD traffic; endpoints that receive POST should get a 308.

Common causes

If you are visiting the site

  • The browser shows "too many redirects" (ERR_TOO_MANY_REDIRECTS) because the site bounces between http and https, or between www and non-www, in a loop.
  • A cached 301 from an old configuration keeps sending you to an address the site no longer uses.

If you run the server

  • An HTTPS redirect behind a proxy or CDN: the app sees plain HTTP coming from the proxy (Cloudflare's Flexible SSL mode is the classic case), redirects to https, and the proxy asks again over HTTP, forever.
  • Two layers disagree on the canonical host: nginx sends www to the bare domain while the CMS (the WordPress siteurl setting, for example) sends it back to www.
  • Chains left over from several migrations, such as /a to /b to /c to https://www.example.com/c, each hop adding latency.
  • A 301 used for what was really a temporary move, now cached in browsers so the old URL cannot easily be reused.
  • POST requests to a moved endpoint losing their body because clients re-send them as GET.

How to fix it

If you are visiting the site

  • Clear the site's cookies and cached files (in Chrome: Settings, Privacy and security, Delete browsing data, cached images and files); this also drops stored 301s.
  • Open the page in a private window: if it loads there, a stale redirect or cookie in your normal profile is the problem.

If you run the server

  • Trace every hop with curl -sIL https://example.com/old-page and point each old URL straight at its final destination.
  • Behind a proxy, decide on the https redirect from X-Forwarded-Proto (or switch Cloudflare to Full or Full (strict) SSL) instead of the scheme your app sees on the connection.
  • Enforce the canonical host and scheme in exactly one layer, either the web server or the application, never both.
  • While testing a new rule, use 302 or add Cache-Control: max-age=3600 to the 301, so a mistake does not live on in visitors’ browsers.
  • Switch to 308 for redirected endpoints that receive POST, PUT, PATCH or DELETE.

How to send 301

Express (Node.js)
app.get('/blog/:slug', (req, res) => {
  res.redirect(301, `/articles/${req.params.slug}`);
});
Next.js App Router route handler
// app/blog/[slug]/route.ts
export async function GET(
  request: Request,
  { params }: { params: Promise<{ slug: string }> }
) {
  const { slug } = await params;
  // Response.redirect needs an absolute URL
  return Response.redirect(new URL(`/articles/${slug}`, request.url), 301);
}

// next.config.js: permanent: true sends 308; ask for 301 explicitly
// redirects: async () => [
//   { source: '/blog/:slug', destination: '/articles/:slug', statusCode: 301 }
// ]
Go net/http
mux.HandleFunc("GET /blog/{slug}", func(w http.ResponseWriter, r *http.Request) {
	http.Redirect(w, r, "/articles/"+r.PathValue("slug"), http.StatusMovedPermanently) // 301
})
Python FastAPI
from fastapi import FastAPI
from fastapi.responses import RedirectResponse

app = FastAPI()

@app.get("/blog/{slug}")
def old_blog_post(slug: str):
    # RedirectResponse defaults to 307; say 301 explicitly
    return RedirectResponse(f"/articles/{slug}", status_code=301)
Nginx
# http:// and www. both go to https://example.com, in one hop
server {
  listen 80;
  server_name example.com www.example.com;
  return 301 https://example.com$request_uri;
}

# A single moved page
location = /old-pricing {
  return 301 /pricing;
}

Commonly confused with

301 vs 308
Both are permanent and treated alike by Google; 308 forbids changing POST to GET, while 301 allows it and browsers do it.
301 vs 302
302 is temporary: browsers do not cache it by default and search engines keep the original URL indexed.
301 vs 410
Use 410 Gone when there is no replacement page; a 301 to an unrelated page such as the homepage is treated by Google like a soft 404.

Frequently asked questions

Does a 301 redirect pass SEO value to the new URL?
Google documents a 301 as a strong signal that the target should be the canonical URL, so the new page takes the old one’s place in search results along with its signals. Keep the redirect in place for as long as the old URL still gets links or traffic, ideally a year or more.
How long do browsers cache a 301 redirect?
Without Cache-Control or Expires, a 301 can be cached with no fixed limit, and Chrome and Firefox in practice keep it until the cache is cleared. If you might need to undo the redirect, send it with Cache-Control: max-age set to a period you can live with.
Should I use 301 or 308?
For ordinary pages fetched with GET they behave the same and Google treats them the same. Use 308 when the redirected URL receives POST, PUT or DELETE, since a 301 lets clients turn those into GET and lose the body.
How do I fix ERR_TOO_MANY_REDIRECTS after adding a 301?
Run curl -sIL on the URL to see the loop. It is almost always two rules undoing each other: an https redirect behind a proxy that talks to the origin over HTTP, or host rules in the server and the CMS pointing in opposite directions.

Last reviewed by Arielton Oberek.