Skip to content

307 Temporary Redirect

What is HTTP 307 Temporary Redirect?

307 Temporary Redirect
When you try to send something to a website - like submitting a form or uploadin...
HTTP 307 Temporary Redirect status code illustration

Explain Like I’m 3

You’re trying to give someone a toy, but they’re in a different room right now. Someone tells you ‘Go give it to them in the other room!’ So you walk to the other room and give them the toy. You’re still giving the toy, just in a different place!

Example: You want to hand your drawing to your teacher, but they’re temporarily in the library. You go to the library and hand them the drawing. You didn’t change what you were doing - you still handed them a drawing!

Explain Like I’m 5

When you try to send something to a website - like submitting a form or uploading a picture - sometimes that website has temporarily moved to a different address. The website sends you a ‘307 Temporary Redirect’ message that says ‘I’m over here for now!’ Your browser then sends the exact same thing you were sending, but to the new address. The important part: your browser sends the same kind of request. If you were posting a form, you post that form to the new address. If you were uploading a picture, you upload the picture to the new address. Nothing changes except where you send it. Later, the website will move back to its original address.

Example: You’re submitting your homework through a school website. The website is temporarily using a backup server, so it responds ‘307 - I’m on the backup server!’ Your browser automatically submits your homework to the backup server using the exact same ‘submit homework’ action. You don’t have to do anything different!

Jr. Developer

HTTP 307 Temporary Redirect indicates the target resource has temporarily moved to a different URI, and the client MUST use the same HTTP method when making the redirected request. This is the critical distinction from 302: with 307, POST stays POST, PUT stays PUT, DELETE stays DELETE. If the original request was POST /api/submit with a request body, the redirected request will also be POST to the new URL with the same body. 307 was introduced in HTTP/1.1 (RFC 2616, now RFC 9110) to fix the ambiguity of 302 - where browsers historically changed POST to GET despite the spec saying otherwise. Use 307 for temporary API endpoint moves, load balancing, server maintenance, or A/B testing where method preservation is essential. For web forms where you want POST to become GET (Post-Redirect-Get pattern), use 303 See Other instead. Unlike 301 Permanent Redirect, 307 tells clients and search engines the move is temporary - don’t update bookmarks or cached URLs.

Example: API endpoint /api/v1/orders temporarily moves to /api/v2/orders for migration testing. Client POSTs order data to /api/v1/orders. Server returns 307 with Location: /api/v2/orders. Client automatically POSTs the same order data to /api/v2/orders. Order is created successfully without client code changes.

Code Example

// Express.js implementing 307 Temporary Redirect
const express = require('express');
const app = express();
// Temporary API endpoint redirect
app.post('/api/v1/orders', (req, res) => {
// V1 endpoint temporarily redirects to V2
// POST method and body preserved
res.redirect(307, '/api/v2/orders');
});
app.post('/api/v2/orders', async (req, res) => {
// Receives the same POST request with same body
const order = await db.createOrder(req.body);
res.status(201).json(order);
});
// Load balancing with 307
app.use('/api/heavy-task', (req, res, next) => {
const currentLoad = getServerLoad();
if (currentLoad > 80) {
// Temporarily redirect to backup server
// Preserves POST/PUT/DELETE methods
return res.redirect(307, 'https://backup.example.com/api/heavy-task');
}
next();
});
// Maintenance mode with method preservation
app.use((req, res, next) => {
const inMaintenance = process.env.MAINTENANCE === 'true';
const isMaintenanceEndpoint = req.path.startsWith('/maintenance');
if (inMaintenance && !isMaintenanceEndpoint) {
// Redirect to maintenance server, preserve method
return res.redirect(307, `https://maintenance.example.com${req.path}`);
}
next();
});
function getServerLoad() {
return Math.random() * 100; // Placeholder
}

Crash Course

307 Temporary Redirect, defined in RFC 9110 Section 15.4.8 (obsoleting RFC 7231), indicates the target resource resides temporarily under a different URI and the user agent MUST NOT change the request method if it performs an automatic redirection to that URI. Per RFC 9110, the server SHOULD generate a Location header containing the temporary URI. Unlike 302 Found which historically allowed (and caused) method changes, 307 strictly preserves method and body semantics. This distinction exists because HTTP/1.0’s 302 specification said preserve method, but Netscape and IE changed POST to GET for usability, creating de facto standard behavior. HTTP/1.1 formalized this: 302 allows method change (backward compatibility), 303 mandates GET conversion (explicit), and 307 forbids method change (strict). Use cases: temporary API migrations where client code shouldn’t change, load balancing across servers, geographic routing, maintenance mode redirects, A/B testing backend variants, failover scenarios. Key semantic: 307 is temporary - clients should continue using original URI for future requests. For permanent method-preserving redirects, use 308. For temporary redirects where POST should become GET (web forms), use 303. SEO note: 307 doesn’t transfer PageRank - search engines continue indexing original URL. Caching: 307 is cacheable by default but typically isn’t cached without explicit Cache-Control due to temporary nature.

Example: RESTful API migration: /api/users POST endpoint moving to /api/v2/users during phased rollout. Client POSTs user creation to /api/users with JSON body {name: ‘Alice’, email: ‘alice@example.com’}. Server in migration mode returns 307 with Location: /api/v2/users. Client’s HTTP library automatically retries POST to /api/v2/users with identical body. User created successfully. Client code unmodified. If 302 were used, some clients might convert to GET, losing body and failing request.

Code Example

// Advanced 307 Temporary Redirect patterns
const express = require('express');
const app = express();
// Geographic load balancing with 307
const GEO_SERVERS = {
'US': 'https://us.api.example.com',
'EU': 'https://eu.api.example.com',
'APAC': 'https://apac.api.example.com'
};
app.use('/api', (req, res, next) => {
const region = req.headers['cf-ipcountry'] || 'US';
const targetServer = GEO_SERVERS[region] || GEO_SERVERS['US'];
// Check if request is from same region as this server
const thisServer = process.env.REGION;
if (thisServer !== region) {
// Temporarily redirect to regional server
const targetUrl = `${targetServer}${req.originalUrl}`;
return res.redirect(307, targetUrl);
}
next();
});
// Phased API migration with 307
const MIGRATION_PERCENTAGE = 20; // 20% of traffic to v2
app.post('/api/v1/resources', (req, res) => {
// Gradually migrate traffic to v2
const migrateUser = Math.random() * 100 < MIGRATION_PERCENTAGE;
if (migrateUser) {
// Temporary redirect to v2 during testing phase
// Preserves POST method and body
return res.redirect(307, '/api/v2/resources');
}
// Handle in v1
handleV1Creation(req, res);
});
app.post('/api/v2/resources', async (req, res) => {
const resource = await db.createResourceV2(req.body);
res.status(201).json(resource);
});
// Failover with 307
app.use('/api/database-heavy', async (req, res, next) => {
const dbHealthy = await checkDatabaseHealth();
if (!dbHealthy) {
// Database down - redirect to read-replica server
const replicaUrl = `https://replica.example.com${req.originalUrl}`;
// 307 preserves GET/POST/PUT/DELETE
return res.redirect(307, replicaUrl);
}
next();
});
// A/B testing backend variants
app.put('/api/users/:id', (req, res) => {
const userId = req.params.id;
const useNewAlgorithm = isInExperimentGroup(userId);
if (useNewAlgorithm) {
// Temporary redirect to experimental endpoint
// PUT method and body preserved
return res.redirect(307, `/api/experimental/users/${userId}`);
}
// Original implementation
handleUserUpdate(req, res);
});
app.put('/api/experimental/users/:id', async (req, res) => {
const updated = await db.updateUserWithNewAlgorithm(
req.params.id,
req.body
);
res.json(updated);
});
// Helper functions
async function checkDatabaseHealth() {
try {
await db.query('SELECT 1');
return true;
} catch (err) {
return false;
...

Deep Dive

The 307 Temporary Redirect status code, specified in RFC 9110 Section 15.4.8, indicates that the target resource resides temporarily under a different URI and the user agent MUST NOT change the request method if it performs an automatic redirection to that URI. Per RFC 9110, since the redirection can change over time, the client ought to continue using the original effective request URI for future requests. The server SHOULD generate a Location header field containing the preferred URI reference, and the user agent MAY use that Location value for automatic redirection. A 307 response is cacheable by default unless otherwise indicated by the method definition or explicit cache controls, though in practice most implementations don’t cache 307 without explicit directives due to its temporary nature.

Technical Details

Historical evolution of method-preserving redirects: HTTP/1.0 (RFC 1945) defined 302 “Moved Temporarily” with specification requiring method preservation, but browser implementations (Netscape Navigator 2.0, IE 3.0) changed POST to GET for perceived usability (users expected to see result page after form submission, not resubmit form). This de facto behavior became widespread by 1997. HTTP/1.1 (RFC 2068, 1997) introduced 303 See Other (explicit POST→GET conversion) and 307 Temporary Redirect (explicit method preservation) to disambiguate, while retaining 302 for backward compatibility with its ambiguous “may change method” semantics. RFC 2616 (1999) clarified: “If the 307 status code is received in response to a request other than GET or HEAD, the user agent MUST NOT automatically redirect the request unless it can be confirmed by the user, since this might change the conditions under which the request was issued.” RFC 9110 (2022) modernized language but preserved semantics: 307 MUST preserve method, no user confirmation explicitly required (left to implementation).\n\nMethod preservation semantics: When redirecting with 307, the client must use the same HTTP method and include the same request body (if any) in the redirect request. For GET and HEAD, this is straightforward (no body to preserve). For POST, PUT, PATCH, DELETE, the entire request body must be resent to the redirect target. Request headers generally carry over except for hop-by-hop headers (Connection, Keep-Alive, Transfer-Encoding). The Host header changes to match the redirect target. Authorization headers may be stripped when redirecting to different origins for security (RFC 9110 doesn’t mandate this, but many clients implement origin-based auth stripping). Custom application headers (X-API-Key, X-Request-ID) typically carry over.\n\nCaching semantics per RFC 9110: 307 responses are cacheable by default for GET and HEAD requests, subject to Cache-Control and Expires headers. Caching a 307 means storing the redirect mapping, not the final resource. Example: Cache stores “GET /api/users → 307 → /api/v2/users” with max-age=3600. Within that hour, requests to /api/users immediately redirect to /api/v2/users without contacting origin. After expiry, revalidation occurs. However, most CDNs and browsers don’t cache 307 without explicit Cache-Control: max-age due to “temporary” semantics suggesting volatility. Best practice: include Cache-Control: no-store for dynamic 307s (load balancing, failover), or Cache-Control: public, max-age=300 for short-lived cacheable redirects (A/B testing, gradual rollouts).\n\nComparison matrix of redirect codes: 301 (permanent, may change method historically but shouldn’t per RFC 9110), 302 (temporary, may change method due to legacy behavior), 303 (temporary, MUST change to GET), 307 (temporary, MUST preserve method), 308 (permanent, MUST preserve method). For APIs: use 308 for permanent endpoint moves, 307 for temporary/testing, 303 for POST-R…

Code Example

// Production-grade 307 Temporary Redirect implementation\nconst express = require('express');\nconst crypto = require('crypto');\nconst app = express();\n\n// Redirect tracking and safety\nclass RedirectManager {\n constructor() {\n this.allowedDomains = [\n 'api.example.com',\n 'api-v2.example.com',\n 'backup.example.com',\n 'maintenance.example.com'\n ];\n }\n \n // Validate redirect target to prevent open redirects\n validateRedirectUrl(targetUrl, currentHost) {\n try {\n const url = new URL(targetUrl, \`https://${currentHost}\`);\n \n // Only allow HTTPS for external redirects\n if (url.protocol !== 'https:' && url.host !== currentHost) {\n console.warn(\`Blocked non-HTTPS redirect: ${targetUrl}\`);\n return null;\n }\n \n // Check domain whitelist for cross-origin redirects\n if (url.host !== currentHost && !this.allowedDomains.includes(url.host)) {\n console.warn(\`Blocked redirect to untrusted domain: ${url.host}\`);\n return null;\n }\n \n return url.toString();\n } catch (err) {\n console.error('Invalid redirect URL:', err);\n return null;\n }\n }\n \n // Track redirect metrics\n recordRedirect(from, to, reason) {\n console.log(\`307 Redirect: ${from} -> ${to} (${reason})\`);\n // In production: send to metrics/logging service\n }\n}\n\nconst redirectManager = new RedirectManager();\n\n// Helper function for safe 307 redirects\nfunction redirect307(req, res, targetUrl, reason = 'unspecified') {\n const validatedUrl = redirectManager.validateRedirectUrl(targetUrl, req.headers.host);\n \n if (!validatedUrl) {\n return res.status(500).json({\n error: 'Internal Server Error',\n message: 'Invalid redirect configuration'\n });\n }\n \n redirectManager.recordRedirect(req.originalUrl, validatedUrl, reason);\n \n // Set appropriate cache headers\n res.set('Cache-Control', 'no-store, no-cache, must-revalidate, private');\n res.set('Pragma', 'no-cache');\n \n res.redirect(307, validatedUrl);\n}\n\n// Geographic load balancing\nconst GEO_ROUTING = {\n 'US': 'https://us.api.example.com',\n 'GB': 'https://eu.api.example.com',\n 'DE': 'https://eu.api.example.com',\n 'JP': 'https://apac.api.example.com',\n 'AU': 'https://apac.api.example.com'\n};\n\napp.use('/api', (req, res, next) => {\n const country = req.headers['cf-ipcountry'];\n const targetServer = GEO_ROUTING[country];\n \n // Only redirect if we have a regional server for this country\n if (targetServer && targetServer !== \`https://${req.headers.host}\`) {\n const targetUrl = \`${targetServer}${req.originalUrl}\`;\n return redirect307(req, res, targetUrl, 'geo-routing');\n }\n \n next();\n});\n\n// Gradual API migration with canary deployment\nclass CanaryDeployment {\n constructor() {\n this.canaryPercentage = 0;\n this.canaryUsers = new Set();\n }\n \n setCanaryPercentage(percentage) {\n th...

Frequently Asked Questions

What's the difference between 307 Temporary Redirect and 302 Found?

307 guarantees the HTTP method and request body are preserved during the redirect. If you POST to a URL that returns 307, the redirected request will also be POST with the same body. 302 historically allowed (and caused) method changes - browsers converted POST to GET. For modern APIs and forms where method preservation matters, use 307. For simple page redirects where method doesn't matter, 302 is fine (though 307 is clearer).

When should I use 307 instead of 303 See Other?

Use 307 when you need to preserve the HTTP method (POST stays POST, PUT stays PUT). Use 303 when you want POST/PUT/DELETE to become GET - this is the Post-Redirect-Get pattern for web forms. Example: After form submission, use 303 to redirect to a confirmation page (prevents duplicate submissions on refresh). For API endpoint migration where POST must stay POST, use 307.

Should I use 307 or 308 for API versioning?

Use 307 for temporary API migrations during testing/gradual rollout (signals clients to keep using old URL in the future). Use 308 for permanent API migrations (signals clients can update to new URL). Example: Testing v2 with 10% of traffic - use 307. Permanently deprecating v1 - use 308. The key: 307 is temporary, 308 is permanent.

Are 307 redirects cacheable?

Yes, 307 responses are cacheable by default per RFC 9110, but most implementations don't cache them without explicit Cache-Control headers due to their temporary nature. Best practice: use Cache-Control: no-store for dynamic 307s (load balancing, failover), or Cache-Control: public, max-age=300 for short-lived cacheable redirects (A/B testing with 5-minute cache).

How do 307 redirects affect SEO?

307 redirects don't transfer PageRank or link equity - search engines continue indexing and ranking the original URL because the move is temporary. This is exactly what you want for temporary situations (maintenance, A/B testing, seasonal redirects). For permanent moves that should transfer SEO value, use 301 (allows method change) or 308 (preserves method) instead.

Common Causes

  • API endpoint temporarily moved to new version during migration testing
  • Load balancing redirecting requests to different server or data center
  • Geographic routing sending requests to region-specific servers
  • Maintenance mode redirecting to backup/read-only server
  • A/B testing redirecting subset of users to experimental endpoint
  • Circuit breaker failover redirecting to backup service when primary fails
  • Gradual rollout redirecting percentage of traffic to new implementation
  • Rate limiting redirecting excessive requests to queue service

Implementation Guidance

  • Use 307 for temporary API migrations where method preservation is critical
  • For POST-Redirect-GET pattern (web forms), use 303 See Other instead of 307
  • For permanent endpoint moves, use 308 Permanent Redirect instead of 307
  • Always validate redirect targets to prevent open redirect vulnerabilities
  • Set Cache-Control: no-store for dynamic 307s (load balancing, failover)
  • Include Cache-Control: max-age only for short-lived cacheable redirects
  • Avoid redirect chains - redirect directly to final destination when possible
  • Monitor redirect metrics (count, latency) to identify performance issues
  • Test that client libraries correctly preserve method and body on 307
  • Update to 308 once temporary migration becomes permanent

Comments