HTTP 307 is Temporary Redirect: the resource is temporarily at the URL in Location, and the client must repeat the same request there, with the same method and the same body. RFC 9110 section 15.4.8 forbids a user agent from changing the method when it follows a 307 automatically. That’s the whole difference from 302, where a POST usually comes back as a GET.
The exchange
POST /api/upload HTTP/1.1
Content-Type: application/octet-stream
Content-Length: 1048576
HTTP/1.1 307 Temporary Redirect
Location: https://uploads-2.example.com/api/upload
The client sends the POST and its whole megabyte again, to the new host. Browsers, fetch and curl -L keep the method for 307 and 308. One exception: a fetch whose body is a ReadableStream can’t be replayed, so following a 307 or 308 fails with a network error.
The 307 you didn’t send
Chrome’s DevTools shows 307 Internal Redirect when the browser upgrades http:// to https:// by itself, for a site on its HSTS list. Your server never sent that response, and changing your redirects won’t make it go away. The response header Non-Authoritative-Reason: HSTS gives it away.
Choosing a redirect
302 Found temporary; POST may become GET
303 See Other temporary; always GET the new URL (after a form POST)
307 Temporary Redirect temporary; same method and body
308 Permanent Redirect permanent; same method and body
Don’t use 307 to send a browser on after it posted a password. RFC 9700, the OAuth 2.0 security best current practice, forbids 307 in section 4.12 for redirects of requests that may carry user credentials, because the browser would resend the form fields to the next site; it recommends 303.
Caching and search
307 isn’t heuristically cacheable, so without Cache-Control every request goes to the original URL first. Google treats 307 like 302: it follows it and takes it as a weak hint that the target should be canonical. If the move is permanent, use 308, or 301 for plain pages.
Who sends it
FastAPI’s RedirectResponse defaults to 307, and so does Starlette’s redirect that adds or removes a trailing slash, so a POST /items to a route declared as /items/ comes back as a 307. Next.js redirects with permanent: false send 307.
In your stack
location /api/upload { return 307 https://uploads-2.example.com$request_uri; }
res.redirect(307, 'https://uploads-2.example.com/api/upload'); // Express
return RedirectResponse("https://uploads-2.example.com/api/upload") # FastAPI: 307 by default
return redirect(url, code=307) # Flask
http.Redirect(w, r, "https://uploads-2.example.com/api/upload", http.StatusTemporaryRedirect)