Moved Permanently
ActiveHTTP 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
GET /old-page HTTP/1.1
Host: example.comHTTP/1.1 301 Moved Permanently
Location: https://example.com/new-page
Content-Length: 0Edge 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
| Language | Constant |
|---|---|
| Go | http.StatusMovedPermanently |
| Rust | http::StatusCode::MOVED_PERMANENTLY |
| Python | http.HTTPStatus.MOVED_PERMANENTLY |
| Node.js | http.STATUS_CODES[301] |
| .NET | HttpStatusCode.MovedPermanently |
| Java | HttpURLConnection.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.