HTTP 308 Permanent Redirect: Safe Implementation Guide for Modern Web Development


2 views

The HTTP 308 Permanent Redirect status code was introduced in RFC 7538 (originally RFC 7238) to solve a critical limitation of 301 Moved Permanently. While 301 forces GET method conversion, 308 preserves both the request method and body during redirection - crucial for API endpoints and POST operations.

// Example of 308 response in Node.js
app.post('/old-endpoint', (req, res) => {
  res.status(308)
     .location('/new-endpoint')
     .send('Permanent Redirect');
});

Modern browsers (Chrome 49+, Firefox 51+, Safari 11+, Edge 79+) fully support 308. For unsupported clients:

  • Most modern user agents will treat 308 as 301 (with GET conversion)
  • Some may follow the Location header as with 302/307
  • A few may display the raw 308 response

When implementing 308 redirects:

# Nginx configuration example
location /deprecated/ {
    return 308 https://$host/modern/$request_uri;
}

Key considerations:

  • Always include Location header
  • Maintain consistent URI schemes (HTTP/HTTPS)
  • Consider adding Cache-Control headers
  • Document redirect chains for API consumers

Verify 308 behavior with:

curl -v -X POST http://example.com/old-path \
-H "Content-Type: application/json" \
-d '{"test":"data"}'

Expected response:

HTTP/1.1 308 Permanent Redirect
Location: /new-path

When migrating REST APIs from v1 to v2:

# API Gateway 308 configuration
resources:
  /v1/users:
    post:
      responses:
        308:
          description: Moved to v2
          headers:
            Location:
              type: string
              example: /v2/users

This preserves POST requests during version transitions while maintaining backward compatibility.


The HTTP 308 Permanent Redirect status code (RFC 7238) was introduced to solve a critical limitation of 301 Moved Permanently: the forced method change from POST to GET. Unlike 301, 308 preserves both the request method and body during redirection.


// Example of 308 redirect in Node.js Express
app.post('/old-endpoint', (req, res) => {
  res.status(308).location('/new-endpoint').send();
});

Modern browsers (Chrome 61+, Firefox 51+, Edge 79+, Safari 11+) fully support 308. For unsupported clients:

  • Most browsers will fall back to 302-like behavior (temporary redirect)
  • Some may treat it as 301 (with unwanted method conversion)
  • Legacy systems might fail completely

Consider these real-world scenarios where 308 shines:


# Nginx configuration for API versioning
location /v1/api {
  return 308 https://api.example.com/v2/api$request_uri;
}

Key advantages:

  • Maintains POST/PUT/PATCH request bodies
  • Preserves HTTP headers during redirect
  • Better for RESTful API migrations

For maximum compatibility, implement with fallbacks:


// PHP implementation with User-Agent check
if (strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE 8.0') !== false) {
    header('Location: /new-path', true, 302);
} else {
    header('Location: /new-path', true, 308);
}

308 redirects can be cached indefinitely by browsers, just like 301. Use Cache-Control headers carefully:


# Apache .htaccess example
<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteRule ^old-path$ /new-path [R=308,L,NC]
    Header set Cache-Control "max-age=31536000, immutable"
</IfModule>

Unlike 301/302, 308 prevents:

  • CSRF token leakage via method downgrade
  • Accidental data modification through GET requests
  • Authorization header stripping in some implementations