< HTTP/1.1 301 Moved PermanentlyHTTP 301 Moved Permanently: Permanent Redirect
Learn what 301 redirect means, when to use it vs 302, and how to implement permanent redirects for SEO and URL changes.
- Cacheable
- Yes, by default
- Client action
- Follow Location; update stored links
- Usually sent by
- Origin app, reverse proxy or CDN rule
- Spec
- RFC 9110 §15.4.2
On this page
TL;DR: 301 Moved Permanently means the requested URL has permanently moved to another URL. Update your client or links to
Location; if the destination is wrong, fix the redirect rule and check with browser caching disabled.
What it means
The server is saying the target URL has a new permanent address. Clients should use the new URL for future requests. For Google, a permanent redirect is a canonicalization signal; see the SEO section below. The HTTP exchanges on this page are illustrative.
GET /old-page HTTP/1.1
Host: example.com
HTTP/1.1 301 Moved Permanently
Location: https://example.com/new-page
Content-Length: 0
Two spec details that matter in practice (RFC 9110 section 15.4.2):
- A 301 is heuristically cacheable unless the method or explicit cache controls say otherwise. A cache can reuse it without contacting the server. Set an explicit freshness policy when you need a predictable lifetime; the status alone sets no fixed duration.
- A user agent may change POST to GET on 301. The Fetch redirect algorithm requires that change for browser requests. If a POST or PUT must arrive intact at the new URL, use 308.
What you see in your client
An HTTP 301 is a redirect response. Automatic following can hide it behind the destination’s status, so inspect the first response when your browser and script appear to disagree.
- Axios: in Node’s HTTP adapter,
maxRedirects: 0exposes the redirect. With the default success predicate, it rejects withAxiosError: Request failed with status code 301. Accept the code invalidateStatusto inspectresponse.headers.location. The behavior comes from the HTTP adapter, defaults and error construction. - Browser fetch: follows redirects by default and returns the final response. With
redirect: 'manual', a browser returnstype: 'opaqueredirect',status: 0andok: false, with hidden headers. A directly exposed 301 would also haveok: false, since only 200–299 passes. Use the Network panel or curl for the actualLocation; these rules are in the Fetch Standard. - Python requests:
requests.get(url, allow_redirects=False)exposes 301;raise_for_status()returns normally andresponse.okisTrue. Inspectresponse.headers['Location']. Its error guard raises for 4xx/5xx, while GET follows by default. - curl
-f: accepts 301;-Lenables following. Without-L, inspect the headers using-i. The manual reserves failure code 22 for HTTP responses of 400 or greater. - .NET: set
HttpClientHandler.AllowAutoRedirect = falseto inspect the first response. CallingEnsureSuccessStatusCode()then throwsHttpRequestException; with English resources and reason phraseMoved Permanently, the message isResponse status code does not indicate success: 301 (Moved Permanently).See the redirect setting, guard and message resources. - Spring:
WebClient.retrieve()usesWebClientResponseExceptionfor 4xx/5xx by default.RestTemplate’s default handler likewise treats those ranges as errors; a 301 alone produces neither that exception norHttpClientErrorException.
Who sent it?
curl -sI http://example.com/old-page
HTTP/1.1 301 Moved Permanently
Server: nginx
Location: https://example.com/old-page
Use response headers such as Server as clues, then correlate the request with edge rules and origin logs. A CDN can forward an origin redirect or emit its own; identify the matching rule before editing your app. Follow the full chain and count hops:
curl -sIL -o /dev/null -w '%{num_redirects} hops, final %{url_effective} (%{http_code})\n' http://example.com/old-page
For a visual chain check use the redirect audit tool.
Implementation
nginx
location = /old-page {
return 301 /new-page;
}
# HTTP to HTTPS
server {
listen 80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
Apache
Redirect 301 /old-page /new-page
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
Express
// res.redirect defaults to 302, so pass 301 explicitly
app.get('/old-page', (req, res) => res.redirect(301, '/new-page'))
Next.js
// next.config.js: permanent: true sends 308, not 301
module.exports = {
async redirects() {
return [{ source: '/old-page', destination: '/new-page', permanent: true }]
}
}
Next.js permanent: true emits 308. Use middleware or a platform rule if you specifically need a 301.
Common causes by stack
Express / Rails: Express requires res.redirect(301, '/new-page'); Rails uses redirect_to '/new-page', status: :moved_permanently. Both default to 302 when you omit the status. Check that argument before diagnosing a permanent redirect as a framework default.
Django: redirect('/new-page/', permanent=True) emits 301 with the default preserve_request=False. Inspect the view’s flags; adding preserve_request=True selects 308 instead.
nginx / Apache: nginx’s return 301 and Apache’s Redirect 301 emit permanent moves. For nginx, run nginx -T and inspect the matching server_name, location and return lines; -T prints the loaded configuration as well as checking it.
Cloudflare: Always Use HTTPS answers HTTP requests with 301 to the equivalent HTTPS URL. Check that setting and Redirect Rules when a request redirects before appearing in origin logs. For an HTTPS loop, inspect SSL mode and origin redirect rules together.
AWS ALB: a matching listener redirect action with StatusCode: HTTP_301 returns 301. Inspect the rule’s substitutions for host, protocol, port and path; a stale hostname there can redirect every request to an obsolete site.
Kubernetes ingress-nginx: nginx.ingress.kubernetes.io/permanent-redirect selects 301 unless permanent-redirect-code overrides it. Inspect the Ingress metadata when the backend never sees the request. Its automatic TLS redirect defaults to 308, so distinguish that rule from a permanent-redirect annotation.
The cache trap
Because 301 is cached, a mistake sticks. Typical incidents: a redirect to a staging host that was later removed, an http to https redirect added before the certificate worked, or a loop that persists in the browser after the server is fixed (ERR_TOO_MANY_REDIRECTS). Fixes:
- Test new rules with 302 and promote to 301 once verified.
- Send
Cache-Control: max-age=3600on a 301 you are unsure about, so that cached redirect becomes stale after an hour, accounting for its current age. A new request after expiry can fetch the corrected rule. - To reproduce a “fixed on the server, still broken in my browser” report, run
curl -sI(curl does not cache) and compare with a new private browser session. In Chrome, DevTools Network with Disable cache ticked bypasses the browser cache. - HSTS is a separate policy: while it applies, the browser upgrades HTTP URLs to HTTPS before sending the request. Diagnose that browser upgrade separately from a server-issued 301.
SEO
- Google treats 301 and 308 as a strong signal that the destination should be canonical. Use the code that matches the permanence of the move.
- Google recommends keeping migration redirects for at least a year. Keep useful redirects longer while old links remain in circulation.
- Update internal links and sitemaps to the final URL instead of relying on the redirect.
- Collapse chains (A to B to C) into a single hop; Googlebot follows a limited number of hops and every hop adds latency.
301 vs the other redirects
| Code | Permanence | Method on follow | Use |
|---|---|---|---|
| 301 | Permanent | POST may become GET | Page and site moves, HTTP to HTTPS |
| 302 | Temporary | POST may become GET | Short-lived moves, login bounces |
| 307 | Temporary | Preserved | API temporary redirect |
| 308 | Permanent | Preserved | API endpoint moved for good |
See also 301 vs 302 and 302 vs 307.
Related
- 302 Found
- 307 Temporary Redirect
- 308 Permanent Redirect
- Location header
- 301 vs 308: when a redirected POST must stay a POST.
Frequently asked questions
What does 301 redirect mean?
A 301 tells browsers and search engines the resource has permanently moved to a new URL. Update your bookmarks and links.
What is the difference between 301 and 302?
301 is permanent (update links, transfer SEO). 302 is temporary (keep original URL, check back later). Use 301 for permanent URL changes.
Does 301 pass SEO value?
Google treats 301 as a strong signal that the destination should be canonical. Update links and sitemaps to the final URL and monitor the move in Search Console.
Can 301 change the request method?
Yes. RFC 9110 allows POST to become GET on a 301, and the Fetch Standard requires that change for browser requests. Use 308 when the method and body must be preserved.
Sources
Related
301 vs 302 Redirects
Understand the difference between 301 Moved Permanently and 302 Found redirects. Learn when to use each, how browsers cache them, and their SEO implications.
301 vs 308: Permanent Redirects and POST Requests
301 vs 308: both signal a permanent move, but only 308 preserves POST. Compare browser handling, Google indexing, cache controls, and curl traces.
497 HTTP Request Sent to HTTPS Port (nginx)
nginx 497 means plain HTTP hit an HTTPS port; clients see "400 The plain HTTP request was sent to HTTPS port". Fix with error_page 497. Also 494, 495, 496.
X-Robots-Tag Header: noindex for PDFs and Staging
X-Robots-Tag sends noindex and snippet rules as an HTTP header for PDFs, images and staging sites. nginx, Apache and Cloudflare config, and the robots.txt trap.
HTTP 302 Found: Temporary Redirect
Learn what 302 redirect means, when to use temporary vs permanent redirects, and how 302 differs from 301, 307, and 308.
ERR_TOO_MANY_REDIRECTS: Find and Break the Redirect Loop
Fix ERR_TOO_MANY_REDIRECTS (redirected you too many times): Cloudflare Flexible SSL, WordPress URLs, nginx HTTPS loops and cookie loops, diagnosed with curl.