301 - Moved Permanently
HTTP 301 Moved Permanently means the resource has a new permanent URI and future requests should use it.
Last reviewed: February 12, 2026|Editorial standard: source-backed technical guidance
What Does Moved Permanently Mean?
The resource has permanently moved, so clients and crawlers should stop using the old URI and treat the new location as canonical.
Common Causes
- -Route or domain migration permanently changed canonical URI.
- -Endpoint versioning retired old path in favor of new path.
- -Infrastructure policy enforces permanent host canonicalization.
How to Fix Moved Permanently
- 1Validate the Location target is the intended canonical URI and resolves with expected content.
- 2Update internal links, sitemaps, SDK defaults, and API clients to use the new permanent URI directly.
- 3Remove chained redirects and align CDN or origin cache policy for permanent migration behavior.
Step-by-Step Diagnosis for Moved Permanently
- 1Capture original URI, method, and `Location` target for every 301 emission path.
- 2Verify redirect map has one canonical destination and no conflicting host/path rewrites.
- 3Check client and crawler handling for method preservation and caching behavior.
- 4Retest with direct and redirected calls to validate one-hop permanent migration behavior.
Redirect Map and Canonical Destination Integrity
- -Audit redirect chains for multi-hop or conflicting rules (example: `/a -> /b -> /c` instead of one-hop `/a -> /c`).
- -Verify canonical host/path policy consistency (example: www and non-www rules conflict by environment).
Client, Cache, and Method Behavior Validation
- -Inspect cache persistence of permanent redirects (example: stale 301 cached by browser after rollback).
- -Validate method semantics for non-GET requests through redirect path (example: legacy client rewrites POST unexpectedly).
Implementation Examples
curl -i -X GET https://api.example.com/v1/resource
# Response:
# HTTP/1.1 301 Moved Permanently
# {"error":"Moved Permanently","code":"301"}const response = await fetch('https://api.example.com/v1/resource', {
method: 'GET',
headers: { 'Accept': 'application/json' }
});
if (response.status === 301) {
console.error('Handle 301 Moved Permanently');
}import requests
response = requests.get(
'https://api.example.com/v1/resource',
headers={'Accept': 'application/json'}
)
if response.status_code == 301:
print('Handle 301 Moved Permanently')How to Verify the Fix
- -Request old URIs and confirm they produce a single 301 hop to the canonical destination.
- -Validate crawler, browser, and API clients resolve directly to the new URI after cache refresh cycles.
- -Monitor redirect-chain metrics and old-URI traffic to confirm permanent migration stabilization.
How to Prevent Recurrence
- -Maintain a canonical URL registry and enforce it in edge, application, and documentation pipelines.
- -Add migration tests that fail on multi-hop redirects, loops, or conflicting host and path rewrites.
- -Schedule redirect deprecation reviews so temporary transitions do not become accidental long-term chains.
Pro Tip
- -version redirect maps like database migrations so every permanent move is auditable and reversible during incident response.
Decision Support
Compare Guide
403 Forbidden vs 404 Not Found: When to Hide Resources
Use 403 for explicit access denial, or 404 to conceal resource existence when security policy requires reducing endpoint and object enumeration risk.
Compare Guide
404 Not Found vs 410 Gone: Missing vs Permanent Removal
Learn when to return 404 (missing or temporary absence) versus 410 (intentional permanent removal), including redirect and cache implications.
Playbook
Resource State Playbook (404 / 410 / ResourceNotFound)
Use this playbook to separate temporary missing-resource lookups from permanent removals, then fix scope, lifecycle, and identifier drift safely.
Official References
Provider Context
This guidance is specific to HTTP services. Always validate implementation details against official provider documentation before deploying to production.