HTTP 301 Moved Permanently

Moved Permanently

HTTP 301 is Moved Permanently: the resource has a new URL for good, given in the Location header. RFC 9110 section 15.4.2 asks clients that can to update their stored links. For historical reasons it also lets a user agent turn a POST into a GET when it follows the redirect; when the method must survive, use 308.

The exchange

GET /blog/old-post HTTP/1.1
Host: example.com

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

Browsers follow the Fetch standard here: after a 301 they turn a POST into a GET without its body, and keep every other method. curl -L does the same unless you pass --post301.

Caching: it sticks

301 is heuristically cacheable (RFC 9110 section 15.1), and browsers keep a 301 that has no cache headers, often for a long time. You can’t recall it from a visitor’s cache, so a wrong 301 keeps sending people to the wrong place after you’ve fixed the server. While testing, send Cache-Control: max-age=3600 with it, or test with 302 and switch once it’s right.

Search engines

Google treats 301 and 308 the same: a strong signal that the target should be the canonical URL. 302 and 307 are only a weak signal. Google’s site-move guide says to keep the redirects for as long as possible, generally at least a year. Point each old URL straight at its final address: Googlebot follows at most 10 redirect hops, and every hop costs visitors a round trip.

Who sends it

  • nginx and Go’s ServeMux, when a directory is requested without its trailing slash.
  • CloudFront, under the “Redirect HTTP to HTTPS” viewer protocol policy.
  • AWS ALB redirect actions, which offer HTTP_301 and HTTP_302 only.

301 or another redirect

Use 308 for an endpoint that takes POST, PUT or DELETE, such as an API route or a form’s action. Use 302, 303 or 307 when the move may be undone: a sale page, a login bounce, an A/B test.

In your stack

server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://example.com$request_uri;
}
Redirect permanent /blog/old-post /articles/new-post
res.redirect(301, '/articles/new-post');                 // Express; the default is 302
return redirect("/articles/new-post", code=301)          # Flask
return redirect("/articles/new-post", permanent=True)    # Django
http.Redirect(w, r, "/articles/new-post", http.StatusMovedPermanently)

Try it in the HTTP Status Codes

301 Moved Permanently

  1. 301Moved Permanently3xx Redirection

    The resource lives at a new URL now. The move is permanent, so caches and search engines should update their copy and stop asking for the old address.

    When to use it

    A renamed page, a site that changed hostnames, the www to apex redirect: the everyday permanent redirect.

    Common mistakes

    Pointing it at something that may move again, and forgetting that clients may repeat a POST as a GET after following it.

    Cacheable by defaultRFC 9110

Every code, by class

Codes marked unofficial come from one server’s own convention — nginx, Cloudflare, cPanel, Microsoft — and are not in the IANA registry.

Runs in your browser. Nothing you enter is sent anywhere.