# Location

> Learn how the Location header specifies redirect URLs or the location of newly created resources. Essential for 201, 301, 302, and other redirect responses.

Source: https://howhttpworks.com/headers/location
Last reviewed: 2026-10-05

> **TL;DR:** `Location` is the response header that tells the client where to go: the redirect target on a 3xx, or the URL of the new resource on a `201 Created`. Relative URLs like `/account` are fine. The status code decides the rest: whether the redirect is permanent, and whether a POST stays a POST (307, 308) or may become a GET (301, 302, 303).

## What is Location?

The value is a URI reference, absolute or relative. It redirects nothing on its own: a `200 OK` with `Location` is still a 200. On a `201 Created`, it tells the client where the new resource lives, and the client doesn't navigate there. See [RFC 9110 §10.2.2](https://www.rfc-editor.org/rfc/rfc9110#section-10.2.2).

## How It Works

A redirect with an empty body (examples on this page are trimmed):

```http
HTTP/1.1 307 Temporary Redirect
Location: /new-endpoint
Content-Length: 0
```

A client that follows it repeats the request, same method included, at `/new-endpoint`. Depending on settings, an API client may hand you the 3xx instead of following it.

## Redirect Status Codes

- `301` and `308` indicate a permanent move; `302` and `307` are temporary.
- `301` and `302` permit a client to change POST to GET. `307` and `308` preserve the method.
- `303` tells the client to fetch a different resource with GET (or HEAD). The POST is not replayed.

These rules come from [RFC 9110 §15.4](https://www.rfc-editor.org/rfc/rfc9110#section-15.4). "Temporary" doesn't mean uncacheable: a `302` is cached when its `Cache-Control` or `Expires` allows it. `301` and `308` are heuristically cacheable, so a browser may remember them even without explicit caching headers.

## URL Formats

```http
Location: /account
Location: ../account
Location: https://example.com/account
```

Relative values resolve against the URL that was requested. Watch out for values starting with `//`: that's a protocol-relative URL, which keeps the scheme but names a host, so `//evil.example/` sends the user to another site. A leading slash doesn't make a value a local path.

## Real-World Examples

To redirect a moved route in nginx and keep the method, put this inside a `server` block:

```nginx
location = /old-endpoint {
    return 308 /new-endpoint;
}
```

The nginx [`return`](https://nginx.org/en/docs/http/ngx_http_rewrite_module.html#return) directive accepts a local URI like this one and builds the full redirect URL from the request scheme and server configuration. Behind a proxy, check the `Location` nginx actually sends.

## SEO Considerations

When a page moves for good, send `301` or `308` to its replacement. [Google treats permanent redirects as a strong canonicalization signal](https://developers.google.com/search/docs/crawling-indexing/301-redirects), though the index updates on Google's schedule, not instantly.

Map each old URL to its real replacement: `/old-report` should go to the new report, not to the homepage along with every other removed URL. Then check what the replacement returns; a correct first hop may lead into a loop.

## Security Considerations

Copying a `next` query parameter straight into `Location` creates an open redirect that phishers can abuse. For a small, fixed set of destinations, use an allowlist:

```javascript
app.get('/continue', (req, res) => {
  const allowed = new Set(['/account', '/orders'])
  const target = allowed.has(req.query.next) ? req.query.next : '/account'
  res.redirect(303, target)
})
```

[Express's `res.redirect()`](https://expressjs.com/en/5x/api/response/#res.redirect) sends whatever target you give it; validating it is your job. The allowlist here rejects absolute URLs, protocol-relative URLs and any path not on the list.

## Getting Location right

On a `201`, include `Location` whenever the new resource has its own URL. It isn't mandatory: without it, RFC 9110 §15.3.2 says the request's target URI identifies the created resource.

On redirects, set the status you mean explicitly, then check the header that comes out. A 3xx without `Location` gives the client no target, and browsers handle that differently, so there's no single error message to look for.

Creation headers, with the JSON body left out:

```http
HTTP/1.1 201 Created
Location: /reports/42
Content-Type: application/json
```

The client can store `/reports/42` and fetch it later; the 201 doesn't ask it to go there now. A `303` after a form submission is different: it does ask the client to go fetch that URL.

## Common Patterns

After a successful form POST, answer with `303` to send the browser to a GET confirmation page (the POST/redirect/GET pattern). The redirect won't resend the POST, but users can double-submit, so keep server-side duplicate protection.

Check each hop with plain GET requests. Leave out `-X POST` when you test how redirects change methods: curl applies an explicit `-X` method to every request it follows, which hides the change you're testing:

```bash
curl -sS -D - -o /dev/null https://example.com/old-endpoint
curl -L -v -o /dev/null https://example.com/old-endpoint
```

The first command shows only the first response; [`-L` follows the redirects](https://curl.se/docs/manpage.html#--location).

In Express, once the form's operation has completed, end the handler with:

```javascript
res.redirect(303, '/confirmation')
```

Send it only after the write has committed, not while async persistence is still running. If the next endpoint needs the original method and body, use `307` or `308` instead of POST/redirect/GET.

## Related Headers

- [Refresh](https://howhttpworks.com/headers/refresh)
- [Content-Location](https://howhttpworks.com/headers/content-location)
- [Cache-Control](https://howhttpworks.com/headers/cache-control)
