skip to content

A redirect response arrives with the HTTP header `Location: /v2/users?page=2` instead of a full URL. How does the client work out the exact URL to request next, and what rules govern that resolution?

level: juniorimportance: should knowfreq 42%

answer

  1. Location = URI reference, may be relative
  2. Base = the effective request URI of that hop
  3. Root-relative keeps scheme+host+port; //host keeps scheme only
  4. Relative path drops the base query string
  5. ASCII only: percent-encode, punycode; CR/LF = header injection

basics

~20 s

The Location value may be a relative reference. The client resolves it against the URL of the request that produced the redirect, inheriting scheme, host and port, then applies normal URI resolution rules - so /v2/users?page=2 becomes https://api.example.com/v2/users?page=2.

solid answer

~50 s

`Location` carries a **URI reference**, not necessarily an absolute URL. When it is relative, the client resolves it against the **effective request URI** - the URL of the request that produced this response - using the reference-resolution rules of RFC 3986: - `https://other.example.com/x` - absolute, replaces everything. - `//cdn.example.com/x` - protocol-relative: keeps the current scheme, replaces host. - `/v2/users` - root-relative: keeps scheme, host and port, replaces the whole path. - `page2` or `../v2/users` - path-relative: resolved against the current path's directory, with dot segments removed. After a chain of redirects each hop resolves against the URL of *that* hop, not the original. The header value must be ASCII, so non-ASCII characters have to be percent-encoded. If `Location` has no fragment, browsers carry the original request's fragment over to the new URL. Older specs required an absolute URI; RFC 9110 explicitly allows relative ones, and all real clients resolve them.

code

http · 9 lines
http
GET /v1/users/42?debug=1 HTTP/1.1
Host: api.example.com

HTTP/1.1 308 Permanent Redirect
Location: /v2/users/42

(next request)
GET /v2/users/42 HTTP/1.1
Host: api.example.com

go deeper

for a junior

Know that Location can be relative and is resolved against the URL you just requested, so /path keeps the same scheme and host.

for a middle

Work through the reference forms - absolute, protocol-relative, root-relative, path-relative - and note that the query string is not inherited and each hop rebases.

for a senior

Add encoding and safety: ASCII-only headers, percent-encoding and punycode, CR/LF injection risk when Location is built from user input, and trailing-slash interactions.

for a principal

Treat redirect targets as part of the API surface: canonical absolute URLs from services behind proxies (where the effective request URI is ambiguous), and the operational cost of relative references across environments.

## What Location contains The `Location` response header names where the client should go next (with a 3xx redirect) or what was created (with a 201). Historically RFC 2616 required an absolute URI; RFC 9110 relaxed this to a **URI reference**, matching what browsers had always done. So a client must be prepared for any of these forms and resolve them. ## The resolution algorithm Resolution follows RFC 3986 section 5, against a **base URI** which is the effective request URI - the full URL the client actually requested to get this response. Given a base of `https://api.example.com/v1/users/42?debug=1`: | Location value | Resolved to | |---|---| | `https://other.example/x` | `https://other.example/x` | | `//cdn.example.com/x` | `https://cdn.example.com/x` (scheme inherited) | | `/v2/users?page=2` | `https://api.example.com/v2/users?page=2` | | `43` | `https://api.example.com/v1/users/43` | | `../accounts/9` | `https://api.example.com/v1/accounts/9` | | `?page=3` | `https://api.example.com/v1/users/42?page=3` | | `#section` | same URL, fragment replaced | Key details: - **The query string does not survive** unless the new reference contains one. A relative path reference drops the base query entirely - a common source of "my filter parameters vanished after the redirect". - **Path-relative means relative to the directory**, i.e. everything up to and including the last `/`. Against base path `/v1/users/42`, the reference `43` resolves to `/v1/users/43`, not `/v1/users/42/43`. Whether a base path ends with a slash changes the answer, which is why trailing-slash canonicalisation and relative links interact badly. - **Dot segments are removed** during resolution, and `..` cannot escape above the root. - **Port is inherited** with the authority when the reference has no authority. ## Chained redirects If hop 1 returns `Location: /b` and the resulting request to `/b` returns `Location: c`, the second reference resolves against `https://host/b`, not against the original URL. Each hop rebases. Clients that cache the original base and resolve every hop against it produce wrong URLs on multi-hop chains. ## Encoding constraints HTTP header field values are ASCII. A `Location` pointing at a path with non-ASCII characters must percent-encode them in UTF-8 (`/caf%C3%A9`). Servers that write raw UTF-8 bytes or, worse, raw spaces into `Location` get inconsistent client behaviour - some clients repair it, some fail. Internationalised host names must be punycode-encoded (`xn--`). Whitespace and control characters in `Location` are also a header-injection vector: if the value is built from user input and the input contains CR/LF, an attacker can inject additional headers or a response body, so servers must reject or strip them. ## Fragments Fragments are never sent to the server. If `Location` contains a fragment, it replaces the original. If it does not, browsers reapply the fragment from the original request URL to the redirect target - so `https://site/page#part2` redirecting to `/new` lands on `/new#part2`. Non-browser clients typically just ignore fragments. ## Practical checks `curl -sI` shows the raw `Location` value for a single hop; `curl -sIL` follows the chain and prints each response, which makes the rebasing visible. If a redirect "goes to the wrong place", print the raw header first - the bug is usually a relative reference that resolves differently than the author assumed, or a lost query string.

  • A redirect target is given as `Location: //cdn.example.com/asset.js`. Where does the client go, and why is that form used?
    The double slash makes it a protocol-relative (network-path) reference: the host is replaced but the current scheme is inherited, so from an https page it resolves to https://cdn.example.com/asset.js. It was popular when sites served both http and https and wanted one URL for both. Today it is discouraged, since sites are https-only and an explicit https:// prevents accidental downgrade on an http base.
  • After a redirect, the original query parameters are gone. What is the likely cause?
    The Location value was a relative path reference without its own query string. URI resolution replaces the whole query component from the reference, so a base query is dropped unless the reference carries one. The server must include the parameters it wants preserved in the Location value explicitly - clients will not merge them back in.

Directions given mid-journey: "turn left at the next street" only makes sense from where you are standing now, and after each turn the next instruction is read from the new position - not from where you started.

saying these in an interview costs you the question

  • Insisting Location must always be an absolute URL
  • Resolving every hop of a redirect chain against the original request URL instead of rebasing per hop
  • Assuming query parameters carry over automatically to the redirect target
  • Treating a path-relative reference as relative to the full path rather than to its directory
  • Writing raw non-ASCII or user-supplied CR/LF into Location

context