HTTP
308 - Permanent Redirect
Getting a 308 Permanent Redirect means the resource permanently moved to a new URL—follow the Location header using the same HTTP method (POST stays POST, PUT stays PUT) and update all references permanently. This client-side redirect (3xx) is like 301 but preserves the request method. Most common when APIs permanently migrate endpoints and method must be preserved, but also appears when domains permanently change, URL structures are permanently restructured, or permanent redirects with method preservation are needed.
#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 permanently migrated. Domain permanently changed. URL structure permanently restructured.
- →Infrastructure: Domain permanently migrated. CDN permanently redirects. Load balancer permanently routes. Reverse proxy permanent redirect.
✓Solutions
- 1Step 1: Diagnose - Check DevTools Network tab Location header—308 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 308 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 308 Permanent Redirects
1// Client-side: Handle 308 with method preservation and permanent update
2async function fetchWithPermanentRedirect(url, method = 'GET', body = null) {
3 let response = await fetch(url, {
4 method,
5 headers: body ? { 'Content-Type': 'application/json' } : {},
6 body: body ? JSON.stringify(body) : null,
7 redirect: 'manual', // Handle redirect manually
8 });
9
10 if (response.status === 308) {
11 const newUrl = response.headers.get('Location');
12 console.log('Permanent redirect to:', newUrl);
13
14 // Update stored URL permanently
15 updateApiBaseUrl(url, newUrl);
16
17 // 308 preserves original method (POST stays POST, PUT stays PUT)
18 response = await fetch(newUrl, {
19 method, // Same method as original
20 headers: body ? { 'Content-Type': 'application/json' } : {},
21 body: body ? JSON.stringify(body) : null,
22 });
23 }
24
25 return response.json();
26}
27
28// Update API base URL permanently
29function updateApiBaseUrl(oldUrl, newUrl) {
30 // Update in-memory config
31 API_BASE_URL = newUrl;
32
33 // Update localStorage permanently
34 localStorage.setItem('api-base-url', newUrl);
35
36 // Update all cached URLs
37 const cachedUrls = JSON.parse(localStorage.getItem('cached-urls') || '{}');
38 cachedUrls[oldUrl] = newUrl;
39 localStorage.setItem('cached-urls', JSON.stringify(cachedUrls));
40
41 // Update any stored endpoints
42 const endpoints = JSON.parse(localStorage.getItem('endpoints') || '{}');
43 Object.keys(endpoints).forEach(key => {
44 if (endpoints[key] === oldUrl) {
45 endpoints[key] = newUrl;
46 }
47 });
48 localStorage.setItem('endpoints', JSON.stringify(endpoints));
49}
50
51// POST request with 308 redirect
52async function postWithPermanentRedirect(url, data) {
53 let response = await fetch(url, {
54 method: 'POST',
55 headers: { 'Content-Type': 'application/json' },
56 body: JSON.stringify(data),
57 redirect: 'manual',
58 });
59
60 if (response.status === 308) {
61 const newUrl = response.headers.get('Location');
62 updateApiBaseUrl(url, newUrl);
63
64 // POST method is preserved
65 response = await fetch(newUrl, {
66 method: 'POST', // Still POST, not GET
67 headers: { 'Content-Type': 'application/json' },
68 body: JSON.stringify(data),
69 });
70 }
71
72 return response.json();
73}
74
75// Usage
76fetchWithPermanentRedirect('/api/data', 'POST', { key: 'value' });Express.js: Configure 308 Permanent Redirects
1// Server-side: Return 308 for permanent redirects with method preservation
2const express = require('express');
3const app = express();
4
5// Permanent redirect with method preservation
6app.all('/api/v1/endpoint', (req, res) => {
7 // 308 preserves the HTTP method and is permanent
8 res.redirect(308, '/api/v2/endpoint');
9});
10
11// POST endpoint with permanent redirect
12app.post('/api/v1/submit', (req, res) => {
13 // Permanently redirect POST to new location
14 // Method will be preserved (POST stays POST)
15 res.redirect(308, '/api/v2/submit');
16});
17
18// API version migration
19app.all('/api/v1/*', (req, res) => {
20 const newPath = req.path.replace('/v1', '/v2');
21 res.redirect(308, newPath);
22});
23
24// Domain migration
25app.all('*', (req, res, next) => {
26 const host = req.headers.host;
27
28 // Permanently redirect old domain to new domain
29 if (host === 'old-domain.com') {
30 return res.redirect(308, `https://new-domain.com${req.url}`);
31 }
32
33 next();
34});
35
36// URL structure change
37app.all('/api/old-structure/:id', (req, res) => {
38 const newUrl = `/api/new-structure/${req.params.id}`;
39 res.redirect(308, newUrl);
40});
41
42// Permanent redirect with cache headers
43app.all('/api/legacy', (req, res) => {
44 res.status(308)
45 .set('Location', '/api/modern')
46 .set('Cache-Control', 'public, max-age=31536000') // Cache for 1 year
47 .end();
48});Nginx: Configure 308 Permanent Redirects
1# Nginx: Configure 308 permanent redirects with method preservation
2server {
3 listen 80;
4 server_name old-domain.com;
5
6 # Permanent redirect entire domain (preserves method)
7 return 308 https://new-domain.com$request_uri;
8}
9
10server {
11 listen 80;
12 server_name api.example.com;
13
14 # Permanent redirect with method preservation
15 location /api/v1/endpoint {
16 return 308 /api/v2/endpoint$is_args$args;
17 }
18
19 # POST endpoint permanent redirect
20 location = /api/v1/submit {
21 return 308 /api/v2/submit$is_args$args;
22 }
23
24 # API version migration
25 location ~ ^/api/v1/(.+)$ {
26 return 308 /api/v2/$1$is_args$args;
27 }
28
29 # URL structure change
30 location ~ ^/api/old-structure/(.+)$ {
31 return 308 /api/new-structure/$1$is_args$args;
32 }
33
34 # Permanent redirect with cache headers
35 location /api/legacy {
36 return 308 /api/modern$is_args$args;
37 add_header Cache-Control "public, max-age=31536000" always;
38 }
39
40 # Or use rewrite for permanent redirect
41 location /api/old {
42 rewrite ^/api/old(.*)$ /api/new$1 permanent;
43 }
44
45 # Note: 308 preserves method, unlike 301 which may change POST to GET↗Related Errors
Provider Information
This error code is specific to HTTP services. For more information, refer to the official HTTP documentation.