Skip to main content
301

Moved Permanently

Active
RFC 9110 §15.4.2Since 1996URL migrations, domain changes, SEO

HTTP 301 Moved Permanently indicates the target resource has been assigned a new permanent URI. Defined in RFC 9110 §15.4.2. Clients and search engines should update their references to use the new URI from the Location header.

Description

The 301 Moved Permanently status code indicates that the target resource has been assigned a new permanent URI. Any future references should use one of the URIs provided in the response.

The server SHOULD include a Location header with the new URI. The response is cacheable by default – browsers and CDNs will cache it indefinitely unless told otherwise.

A 301 passes link equity to the new URL. Search engines update their index to the new URL and consolidate signals from the old. This makes 301 the correct code for domain migrations, URL restructuring, and HTTP-to-HTTPS upgrades.

Examples

Request to old URL
http
GET /old-page HTTP/1.1
Host: example.com
301 Redirect response
http
HTTP/1.1 301 Moved Permanently
Location: https://example.com/new-page
Content-Length: 0

Edge Cases

  • 301 is cached indefinitely by browsers. If you send a 301 to the wrong URL, affected users are stuck until they clear their cache. Test with 'curl -I' before deploying. Consider using 302 while testing and switching to 301 only when confirmed correct.
  • Redirect chains: /old → /intermediate → /new adds an extra RTT per hop and dilutes SEO equity. Run 'curl -L -I https://example.com/old' to trace the full chain. Collapse everything to a single hop.
  • HTTP to HTTPS: 'return 301 https://$host$request_uri' in Nginx is the most common 301 use. Verify the HTTPS target actually returns 200. HTTP→HTTPS→different-domain is two hops – fix the DNS, not add another redirect.
  • Trailing slash normalization: choose /api/users or /api/users/ and 301 from the other consistently. Inconsistency creates duplicate content in search index and breaks bookmarks.
  • POST method: HTTP clients historically change POST to GET on 301. Use 308 Permanent Redirect if you need the POST method preserved through the redirect.

When You'll See This

  • Domain migration (old-domain.com → new-domain.com)
  • URL restructuring (/blog/123 → /articles/123)
  • HTTP to HTTPS migration
  • Removing trailing slashes or normalizing URLs

Implementation References

LanguageConstant
Gohttp.StatusMovedPermanently
Rusthttp::StatusCode::MOVED_PERMANENTLY
Pythonhttp.HTTPStatus.MOVED_PERMANENTLY
Node.jshttp.STATUS_CODES[301]
.NETHttpStatusCode.MovedPermanently
JavaHttpURLConnection.HTTP_MOVED_PERM

History

Introduced in HTTP/1.0 (RFC 1945, 1996). The distinction between 301 and 308 was clarified in RFC 7538 (2015) to address the method-changing behavior.

Related Status Codes

Related Headers

FAQ

What is the difference between 301 and 302?

301 is permanent (search engines update their index, link equity passes). 302 is temporary (search engines keep the old URL, no equity transfer).

Does a 301 redirect pass SEO value?

Yes. A 301 redirect passes most link equity to the new URL. Google treats 301s as a signal to replace the old URL in its index.