HTTP
301 - Moved Permanently
Getting a 301 Moved Permanently means the resource has permanently moved to a new URL—the Location header shows where it went, and you should update all references to use the new URL permanently. This client-side redirect (3xx) tells browsers and search engines the old URL is obsolete. Most common when APIs migrate endpoints, domains change, or URL structures are restructured, but also appears when resources are permanently relocated, old URLs are deprecated, or permanent redirects are configured.
#Common Causes
- →Frontend: Old URL still in use. Bookmarks point to deprecated endpoint. Application code uses outdated API URL. Client hasn't updated to new endpoint.
- →Backend: Resource permanently moved. API endpoint migrated. Domain changed. URL structure restructured. Permanent redirect configured.
- →Infrastructure: Domain migration in progress. CDN redirects to new location. Load balancer routes to new endpoint. Reverse proxy permanent redirect.
✓Solutions
- 1Step 1: Diagnose - Check DevTools Network tab Location header—301 responses show the new permanent URL. Review redirect chain. Check if redirect is actually permanent.
- 2Step 2: Diagnose - Server logs show redirect configuration. Review permanent redirect rules. Check if resource actually moved. Verify new URL is correct.
- 3Step 3: Fix - Client-side: Update all references to new URL permanently. Update bookmarks and saved URLs. Change API base URL in code. Update client configuration.
- 4Step 4: Fix - Server-side: Configure 301 redirect with correct Location header. Ensure new URL is accessible. Set proper cache headers. Update documentation.
- 5Step 5: Fix - Infrastructure: Update DNS if domain changed. Configure CDN permanent redirects. Update load balancer routing. Set reverse proxy redirects.
</>Code Examples
Fetch API: Handle 301 Permanent Redirects
1// Client-side: Handle 301 and update URLs permanently
2async function fetchWithPermanentRedirect(url) {
3 const response = await fetch(url, { redirect: 'manual' });
4
5 if (response.status === 301) {
6 const newUrl = response.headers.get('Location');
7 console.log('Resource moved permanently to:', newUrl);
8
9 // Update stored URL permanently for future requests
10 localStorage.setItem('api-url', newUrl);
11 updateApiBaseUrl(newUrl);
12
13 // Follow redirect to new location
14 return fetch(newUrl);
15 }
16
17 return response.json();
18}
19
20// Update API base URL permanently
21function updateApiBaseUrl(newUrl) {
22 // Update in-memory config
23 API_BASE_URL = newUrl;
24
25 // Update localStorage
26 localStorage.setItem('api-base-url', newUrl);
27
28 // Update any cached URLs
29 const cachedUrls = JSON.parse(localStorage.getItem('cached-urls') || '{}');
30 cachedUrls[oldUrl] = newUrl;
31 localStorage.setItem('cached-urls', JSON.stringify(cachedUrls));
32}
33
34// Automatic permanent redirect handling
35async function fetchWithAutoUpdate(url) {
36 let response = await fetch(url, { redirect: 'manual' });
37
38 // Handle 301 permanently
39 if (response.status === 301) {
40 const newUrl = response.headers.get('Location');
41
42 // Update all references to new URL
43 updateAllReferences(url, newUrl);
44
45 // Follow redirect
46 response = await fetch(newUrl);
47 }
48
49 return response.json();
50}Express.js: Configure 301 Permanent Redirects
1// Server-side: Return 301 for permanently moved resources
2const express = require('express');
3const app = express();
4
5// Permanent redirect for moved endpoint
6app.get('/api/v1/users', (req, res) => {
7 // Resource permanently moved
8 res.redirect(301, '/api/v2/users');
9});
10
11// Or use redirect with explicit status
12app.get('/old-endpoint', (req, res) => {
13 res.status(301)
14 .set('Location', '/new-endpoint')
15 .set('Cache-Control', 'public, max-age=31536000') // Cache for 1 year
16 .end();
17});
18
19// API endpoint migration
20app.get('/api/v1/data', (req, res) => {
21 // Permanently redirect to new version
22 const newUrl = '/api/v2/data';
23 res.status(301)
24 .set('Location', newUrl)
25 .json({
26 message: 'This endpoint has permanently moved',
27 newUrl: newUrl,
28 migrationDate: '2024-01-01',
29 });
30});
31
32// Domain migration
33app.get('*', (req, res, next) => {
34 const host = req.headers.host;
35
36 // Redirect old domain to new domain
37 if (host === 'old-domain.com') {
38 return res.redirect(301, `https://new-domain.com${req.url}`);
39 }
40
41 next();
42});
43
44// URL structure change
45app.get('/api/old-structure/:id', (req, res) => {
46 const newUrl = `/api/new-structure/${req.params.id}`;
47 res.redirect(301, newUrl);
48});Nginx: Configure 301 Permanent Redirects
1# Nginx: Configure permanent redirects
2server {
3 listen 80;
4 server_name old-domain.com;
5
6 # Permanent redirect entire domain
7 return 301 https://new-domain.com$request_uri;
8}
9
10server {
11 listen 80;
12 server_name api.example.com;
13
14 # Permanent redirect for moved endpoint
15 location /api/v1/users {
16 return 301 /api/v2/users$is_args$args;
17 }
18
19 # Permanent redirect with cache headers
20 location /old-endpoint {
21 return 301 /new-endpoint$is_args$args;
22 add_header Cache-Control "public, max-age=31536000" always;
23 }
24
25 # URL structure change
26 location ~ ^/api/old-structure/(.+)$ {
27 return 301 /api/new-structure/$1$is_args$args;
28 }
29
30 # Or use rewrite for permanent redirect
31 location /api/legacy {
32 rewrite ^/api/legacy(.*)$ /api/v2$1 permanent;
33 }
34}↗Related Errors
Provider Information
This error code is specific to HTTP services. For more information, refer to the official HTTP documentation.