# Redirect visitors and set headers

> Redirect a hostname or some of its paths, set the headers that browsers and your origin get, and change the path your origin is asked for.

Source: https://www.coritan.com/docs/websites/rules/

In the dashboard:

- /dashboard/websites/…/rules: https://www.coritan.com/dashboard/websites

A website's **Rules** tab changes what happens to a request on its way between a visitor and one hostname's origin. You can send every visitor of the hostname to another address, or send only some paths elsewhere with redirect rules. You can also set the response headers that visitors' browsers get, add request headers for your origin, and change the path your origin is asked for.

## Before you begin

- The hostname is on Coritan's network: its `A` or `AAAA` record on the domain's **DNS** tab is **Proxied**, or it has a web proxy on the **Origin** tab ([Web proxies and DNS records](/docs/proxies/web-proxies/#web-proxies-and-dns-records)).
- Everything on this tab comes with every plan.

## Choose the hostname

1. In the dashboard, go to [Websites](https://www.coritan.com/dashboard/websites), open the domain and select the **Rules** tab.
2. When the domain has more than one hostname on Coritan's network, choose one in **Settings for** at the top of the tab. The other tabs keep the hostname you choose.

Every card on the tab applies to the chosen hostname only. The **Response headers**, **Request headers to your origin** and **Path rewrite** cards each have their own save button. Until you save, a card says `Your changes take effect when you save them.`, and **Discard** puts back what is saved.

## Redirect the whole hostname {#whole-hostname}

A redirect of the whole hostname sends every visitor to another address, such as from `example.com` to `https://www.example.com`. Coritan's network then answers every request for the hostname itself and never asks your origin. It sends visitors on over HTTPS, so the destination starts with `https://`.

1. On the **Redirect the whole hostname** card, select **Set redirect**.
2. Enter the **Destination**, such as `https://www.example.com`. It can end in a path, such as `https://www.example.com/shop`.
3. Choose the **Status code** ([Choose a status code](#status-codes)).
4. Leave **Keep the request path** selected to send each visitor to the same path at the destination. Clear it to send every visitor to the destination itself.
5. Leave **Keep the query string** selected to add the visitor's query string, such as `?page=2`, to the address.
6. Check the example under the fields, such as `For example, example.com/blog?page=2 goes to https://www.example.com/blog?page=2.`, then select **Save redirect**.

The card then says `Every visitor goes to https://www.example.com`, with the status code and an example. Select **Edit** to change the redirect. **Remove** turns it off at once, and visitors reach your origin again.

When the destination ends in a path, **Keep the request path** adds the visitor's path after it: with `https://www.example.com/shop`, a visitor who opens `example.com/blog` goes to `https://www.example.com/shop/blog`. A visitor's path that already starts with `/shop` keeps it once.

The redirect answers before **Always use HTTPS**, so a visitor who opens `http://example.com` goes straight to the destination. While it is on, your redirect rules, both header lists and the path rewrite do nothing, and the tab says so under the card. The destination needs a site that answers, such as another hostname on Coritan's network.

## Redirect rules {#redirect-rules}

Redirect rules send visitors who open some paths to another address, such as an old page to its new place. Every other request reaches your origin as before.

### Add a redirect rule

1. On the **Redirect rules** card, select **New rule…**.
2. Choose the **Match**: **Exact path**, **Path prefix** or **Regular expression** ([How rules match](#how-rules-match)).
3. Enter the **Source path**, such as `/old-page`.
4. Enter the **Destination**: a full URL, such as `https://www.example.com/new-page`, or a path on this hostname that starts with a slash, such as `/new-page`.
5. Choose the **Status code** ([Choose a status code](#status-codes)).
6. Set the **Priority**. Coritan's network checks rules with a higher priority first.
7. Leave **Keep the query string** on to add the visitor's query string to the destination.
8. Leave **Enabled** on, then select **Create rule**.

When something in the dialog needs fixing, it says what under the field, and the rule is not created until you fix it ([Troubleshooting](#troubleshooting)).

### How rules match

Coritan's network checks the rules that are on from the highest priority down, and the first rule that matches answers. Give rules that can match the same path different priorities, because Coritan's network can check two rules with the same priority in either order.

A rule tests the path only. The query string takes no part in the match, and the path is read as the visitor sent it, so a space is `%20`.

**Exact path**
: The path must equal the source path, letter case included. `/old-page` matches neither `/old-page/` nor `/Old-Page`.

**Path prefix**
: Every path that starts with the source path matches. `/old-blog` matches `/old-blog/post-1` and also `/old-blogroll`, so end the source path with a slash, such as `/old-blog/`, to match one folder only. Every matching path goes to the same destination, without the rest of its path.

**Regular expression**
: Coritan's network tests the pattern against the start of the path, with Python's regular expression syntax. End the pattern with `$` to match the whole path. The destination can use the pattern's groups as `\1` or `\g<1>`: with `^/blog/(\d+)/?$` and the destination `/posts/\1`, a request for `/blog/42` goes to `/posts/42`. Write a named group as `(?P<name>…)`.

A destination that is a path stays on the same hostname over HTTPS, so `/new-page` sends a visitor on `www.example.com` to `https://www.example.com/new-page`. With **Keep the query string** on, Coritan's network adds the visitor's query string after a `?`, or after a `&` when the destination has a query string already.

Rules answer after **Always use HTTPS**, so a plain HTTP request goes to HTTPS first and the rule answers the HTTPS request. They answer before the WAF, **Under attack mode**, **Hotlink protection** and the cache.

### Change, turn off or delete a rule

The **Redirect rules** card lists the rules from the highest priority down, with the **Source** and its match, the **Destination**, the **Code**, the **Priority** and whether the rule is **Enabled**.

- To turn a rule off or on, use its switch in the **Enabled** column. A rule that is off stays in the list and never matches.
- To change a rule, open its menu, select **Edit rule…**, make the changes and select **Save rule**.
- To delete a rule, open its menu and select **Delete rule…**. The dialog asks `Delete the redirect for /old-page?`. Select **Delete rule**, and requests for that path reach your origin again.

> [!CAUTION]
> You cannot undo deleting a rule. To stop a rule for a while, turn it off instead.

## Choose a status code {#status-codes}

**301 Moved permanently** and **308 Permanent redirect**
: Permanent. Browsers remember the new address, and search engines list it in place of the old one.

**302 Found** and **307 Temporary redirect**
: Temporary. Browsers and search engines keep the old address and ask it again next time.

With **307 Temporary redirect** and **308 Permanent redirect**, a form sent to the old address goes on to the new one with its data. Browsers keep `301` and `308` redirects and follow them without asking again, so use `302` or `307` while you try a redirect out.

## Response headers {#response-headers}

Response headers come with every page and file your site sends to visitors' browsers. They tell the browser how to treat the page, such as whether another site may show it in a frame. Coritan's network adds the ones you set to every answer from your site, whether your origin or Coritan's cache sends it. Each one replaces a header of the same name from your origin. The pages Coritan's network writes itself, such as its error pages, its redirects and the Under attack mode question, do not get them.

1. On the **Response headers** card, select **Add header**.
2. Enter the header's name, such as `Permissions-Policy`, and its value, such as `camera=(), microphone=()`.
3. Add more headers the same way. To take one out, select the cross beside it.
4. Select **Save response headers**.

A hostname can have up to 20 response headers. A name is 1–64 letters, digits and symbols with no spaces, and each name can appear once, in any letter case. A value is up to 1,024 printable ASCII characters on one line, and Coritan keeps it without spaces at its start or end. When you save, the card shows each problem under the header it belongs to, in the same words as the API.

Coritan's network writes some headers itself, so a hostname cannot set them: `Content-Type`, `Content-Length`, `Content-Encoding`, `Content-Range`, `Transfer-Encoding`, `Connection`, `Keep-Alive`, `Upgrade`, `Proxy-Connection`, `TE`, `Trailer`, `Set-Cookie`, `Strict-Transport-Security`, `Alt-Svc`, `Date`, `Proxy-Authenticate`, `Proxy-Authorization`, `X-Cache`, `Coritan-Ray`, `Coritan-Origin` and every name that starts with `grpc-`. To send `Strict-Transport-Security`, turn on [HSTS](/docs/websites/ssl/encryption-mode/#hsts) on the **SSL/TLS** tab.

### Add the security headers {#security-headers}

Under **Security headers**, the card lists three headers that most sites should send, each marked **Added** or **Not added**:

`X-Content-Type-Options: nosniff`
: Browsers treat each file as the type your site says it is, so a file sent as a picture or text cannot run as a script.

`X-Frame-Options: SAMEORIGIN`
: Only pages on this hostname can show its pages inside a frame, so another site cannot trick visitors into clicking on them.

`Referrer-Policy: strict-origin-when-cross-origin`
: When visitors follow a link to another site, it learns only your hostname and never the page they were on.

1. Select **Add security headers**. It adds the ones your list lacks, and leaves a header you already have, such as your own `X-Frame-Options`, as it is.
2. Select **Save response headers**.

**Add security headers** is unavailable once the list has all three. It is also unavailable when they would take the list past 20 headers, and the card then says how many to remove first.

## Request headers to your origin {#request-headers}

Coritan's network adds these headers to every request it forwards to your origin. Your server can check one, such as a secret value, to know that a request came through Coritan's network.

1. On the **Request headers to your origin** card, select **Add header**.
2. Enter the header's name, such as `X-Site-Key`, and its value.
3. Select **Save request headers**.

A visitor can send a header of the same name, and your origin then gets both, with the visitor's first. Coritan's network sets `X-Forwarded-For`, `X-Real-IP` and `X-Forwarded-Proto` itself, so your origin gets its values in place of yours. It does not pass on `Forwarded` or the headers that only describe one connection, such as `Connection`, `Keep-Alive`, `Upgrade` and `Transfer-Encoding`. The card says so under any such header.

## Path rewrite {#path-rewrite}

A path rewrite changes the path Coritan's network asks your origin for, such as when your site lives in a folder on its server. Visitors keep the address they opened.

1. On the **Path rewrite** card, fill in **Remove from the start of the path**, **Add to the start of the path**, or both.
2. Check the example under the fields, such as `A visitor's /blog/hello reaches your origin as /hello.`
3. Select **Save path rewrite**.

With `/blog` in **Remove from the start of the path**, only whole parts of the path count:

- `/blog/hello` reaches your origin as `/hello`.
- `/blog` reaches it as `/`.
- `/blogroll` reaches it as `/blogroll`.

With `/v2` in **Add to the start of the path**, a path that already starts with `/v2` stays as it is:

- `/hello` reaches your origin as `/v2/hello`.
- `/` reaches it as `/v2/`.

With both, Coritan's network removes first, then adds. With `/api` removed and `/v2` added, `/api/users?id=7` reaches your origin as `/v2/users?id=7`.

- Each field takes a path such as `/blog`, up to 255 characters, with no spaces, `?` or `#`. Coritan adds the slash at the start when you leave it out, and drops one at the end.
- The query string stays as it is.
- When your origin answers with a redirect to a rewritten path, Coritan's network changes the address back to the one visitors use.

To stop rewriting, empty both fields and select **Save path rewrite**.

## Result

Each change shows a confirmation that names the hostname:

- `Redirect saved for example.com.` or `Redirect removed from example.com.`
- `Redirect rule created for example.com.`, `Redirect rule updated for example.com.`, `Redirect for /old-page on example.com turned off.` or `Redirect rule deleted from example.com.`
- `Response headers saved for example.com.`
- `Request headers saved for example.com.`
- `Path rewrite saved for example.com.`

A request that a redirect answers gets the status code you chose, with the destination in its `Location` header. `curl -I https://example.com/old-page` shows both, and `curl -I https://example.com/` shows the response headers.

## Troubleshooting

The tab says `example.com is not on our network yet`
: No hostname at the domain or under it is on Coritan's network on your account. Select **Proxy a DNS record** to turn **Proxied** on for an `A` or `AAAA` record on the **DNS** tab, or **Set up a web proxy** to forward a name to any server from the **Origin** tab.

`Start the destination with https://. Our network sends every visitor on over HTTPS.`
: The whole-hostname destination starts with `http://`. Coritan's network sends visitors on over HTTPS only, so enter it with `https://`.

`The destination is a full URL, such as https://www.example.com.`
: The whole-hostname destination has no `https://` at its start. Enter the full address.

`Leave ? and # out of the destination. Keep the query string passes on the visitor's own query.`
: The whole-hostname destination cannot hold a query string or a fragment. Enter it without them, and leave **Keep the query string** selected to pass on the visitor's own.

`Enter a hostname after https://, such as https://www.example.com.` or `Enter the destination without spaces.`
: The destination is not an address. Check its spelling.

`example.com cannot redirect to itself. Enter another hostname.`
: The destination is the hostname you are redirecting, which would send visitors round in a loop. To send `http://` visitors to `https://`, turn on [Always use HTTPS](/docs/websites/ssl/encryption-mode/#always-use-https) on the **SSL/TLS** tab. To move a page within the hostname, add a redirect rule.

`Keep the destination to 253 characters after https://.`
: Shorten the destination.

`Enter a source path, such as /old-page.` or `Enter a destination, such as /new-page.`
: The field is empty. Fill it in.

`Source paths start with a slash, such as /old-page.`
: An exact or prefix rule takes a path, such as `/old-page`, and never a full URL. To redirect the whole hostname, use [Redirect the whole hostname](#whole-hostname).

`The regular expression does not compile. Check its brackets and backslashes.`
: The pattern has a syntax error, such as a bracket that is never closed. Correct it.

`Write a named group as (?P<name>…), the form our network reads.`
: Coritan's network reads Python's syntax, and a pattern with `(?<name>…)` would never match. Write `(?P<name>…)` instead.

`The destination is a full URL, such as https://www.example.com/new-page, or a path on this hostname, such as /new-page.`
: Start the rule's destination with `https://`, `http://` or `/`.

`Could not create the rule:` or `Could not save the rule:`, with a reason
: Coritan did not take the rule, and the dialog keeps what you entered. Fix what the reason names and try again.

A rule never matches
: Check that the rule is on, and that the path matches letter for letter. A rule with a higher priority can answer first, and no rule answers while the whole hostname is redirected.

Visitors go round in a loop
: The destination matches the rule's own source, such as a prefix rule for `/docs` that sends visitors to `/docs/new`. Use an exact rule, end a regular expression with `$`, or choose a destination outside the source path.

Visitors still get a redirect you changed or deleted
: Browsers keep `301` and `308` redirects and follow them without asking again. Clear the browser's cache to test, and use `302` or `307` while you try a redirect out.

`Set-Cookie is a header our network sets, so a website cannot set it.`
: Coritan's network writes that header itself ([Response headers](#response-headers) lists them). Leave it out of the list, and send it from your origin when your site needs it.

`Strict-Transport-Security comes from the website's HSTS settings. Turn HSTS on there instead.`
: Select **Open SSL/TLS** under the header and turn on [HSTS](/docs/websites/ssl/encryption-mode/#hsts) there. Then take the header out of the list.

`"X Robots Tag" is not a header name.`
: A header name has no spaces, and uses only letters, digits and the symbols the message lists. Write `X-Robots-Tag`.

`X-Frame-Options and x-frame-options are the same header. Send it once.`
: Letter case does not tell headers apart. Remove one of them.

`The value of X-Robots-Tag can hold only printable ASCII: letters, digits, spaces and punctuation.`
: The value holds a character such as `é` or a curly quote. Type it again with plain characters. A request header's value follows the same rule.

`The value of Content-Security-Policy is longer than 1024 characters.`
: Shorten the value.

`A website can set up to 20 response headers.`
: Remove the headers you do not need before you add more.

`Each response header needs a name.` or `Each request header needs a name.`
: A row has a value and no name. Enter the name, or remove the row.

`Enter a path such as /blog, without spaces, ? or #.` or `Keep the path to 255 characters.`
: A path rewrite field holds something other than a path. Enter only the path, such as `/blog`.

`Could not change the response headers for example.com`, with a reason
: The change did not save, and the card keeps what you typed. The other cards say `Could not change the request headers`, `Could not change the path rewrite` or `Could not change the redirect`. Fix what the reason names, then save again.

Another site of yours can no longer show your pages in a frame
: `X-Frame-Options: SAMEORIGIN` lets only pages on the same hostname do that. Remove `X-Frame-Options` from the list and select **Save response headers**.

## Related

- [How web proxies work](/docs/proxies/web-proxies/)
- [Protect a website](/docs/websites/waf/)
- [Choose an encryption mode](/docs/websites/ssl/encryption-mode/)
- [Redirect requests with rules](/docs/proxies/web-proxies/redirect-rules/), the same redirects from **Edge Proxy**

## With the API

The redirect of the whole hostname, both header lists and the path rewrite are fields of the hostname's web proxy. Read them with `GET /api/v1/proxy/routes/{route_id}`, and change them with `PATCH /api/v1/proxy/routes/{route_id}`, sending only the fields to change. `GET /api/v1/proxy/routes` lists your web proxies, and a web proxy's `id` there is its `route_id`.

```bash
curl -X PATCH https://api.coritan.com/api/v1/proxy/routes/31 \
  -H "Authorization: Bearer $CORITAN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"response_headers": {"X-Content-Type-Options": "nosniff", "X-Frame-Options": "SAMEORIGIN", "Referrer-Policy": "strict-origin-when-cross-origin"}, "custom_headers": {"X-Site-Key": "7f3a9c2e51b8"}, "strip_path_prefix": "/blog"}'
```

The answer is `{"message": "Route updated"}`.

`redirect_to`
: The destination of the whole-hostname redirect without `https://`, such as `www.example.com` or `www.example.com/shop`. Coritan's network puts `https://` in front of it. An empty string turns the redirect off. A leading `https://` or `http://` you send is dropped.

`redirect_status_code`
: `301`, `302`, `307` or `308`.

`redirect_preserve_path` and `redirect_preserve_query`
: **Keep the request path** and **Keep the query string**, as `true` or `false`.

`response_headers`
: An object of names and values, such as `{"X-Frame-Options": "SAMEORIGIN"}`, with the limits in [Response headers](#response-headers). `{}` or `null` removes them all. The API answers `422` with the reason, in the same words as the card, when the list breaks a limit.

`custom_headers`
: The request headers to your origin, as an object of names and values. `{}` removes them all. The API keeps what you send without checking it, so send names without spaces and values in printable ASCII.

`strip_path_prefix` and `upstream_path_prefix`
: **Remove from the start of the path** and **Add to the start of the path**, such as `/blog`. An empty string clears one.

To redirect the whole hostname:

```bash
curl -X PATCH https://api.coritan.com/api/v1/proxy/routes/31 \
  -H "Authorization: Bearer $CORITAN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"redirect_to": "www.example.com", "redirect_status_code": 301, "redirect_preserve_path": true, "redirect_preserve_query": true}'
```

### Redirect rules with the API

List the rules with `GET /api/v1/proxy/routes/{route_id}/redirect-rules`. The answer is a list, from the highest priority down:

```bash
curl https://api.coritan.com/api/v1/proxy/routes/31/redirect-rules \
  -H "Authorization: Bearer $CORITAN_TOKEN"
```

```json
[
  {
    "id": 7,
    "source_pattern": "/old-page",
    "match_type": "exact",
    "target_url": "/new-page",
    "status_code": 301,
    "preserve_query": true,
    "priority": 10,
    "enabled": true,
    "created_at": "2026-09-02 08:15:40"
  }
]
```

Add a rule with `POST /api/v1/proxy/routes/{route_id}/redirect-rules`. `source_pattern` and `target_url` are required. `match_type` is `exact`, `prefix` or `regex`, `exact` by default. `status_code` is `301`, `302`, `307` or `308`, `301` by default. `preserve_query` and `enabled` are `true` by default, and `priority` is `0`.

```bash
curl -X POST https://api.coritan.com/api/v1/proxy/routes/31/redirect-rules \
  -H "Authorization: Bearer $CORITAN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"source_pattern": "^/blog/(\\d+)/?$", "match_type": "regex", "target_url": "/posts/\\1", "status_code": 308, "priority": 20}'
```

The answer is `201`:

```json
{"id": 8, "message": "Redirect rule created"}
```

The API does not check the source path or the destination the way the dashboard does. Start an exact or prefix source path with a slash, write a regular expression in Python's syntax, and send a destination that is a full URL or a path.

Change a rule with `PATCH /api/v1/proxy/routes/{route_id}/redirect-rules/{rule_id}`, with only the fields you change. It answers `{"message": "Redirect rule updated"}`, and `400` with `No fields to update` when the body holds none. Delete a rule with `DELETE` on the same path, which answers `{"message": "Redirect rule deleted"}`:

```bash
curl -X DELETE https://api.coritan.com/api/v1/proxy/routes/31/redirect-rules/8 \
  -H "Authorization: Bearer $CORITAN_TOKEN"
```

A `match_type` or `status_code` outside the lists answers `400` with `match_type must be exact, prefix, or regex` or `status_code must be 301, 302, 307, or 308`. A web proxy that is not on your account answers `404` with `Route not found`.

## API

- `GET /api/v1/proxy/routes/{route_id}/redirect-rules`: List redirect rules for a route you own (https://www.coritan.com/docs/api/reference/client/reverse-proxy/#op-get-api-v1-proxy-routes-route-id-redirect-rules)
- `POST /api/v1/proxy/routes/{route_id}/redirect-rules`: Create a redirect rule for a route you own (https://www.coritan.com/docs/api/reference/client/reverse-proxy/#op-post-api-v1-proxy-routes-route-id-redirect-rules)
- `PATCH /api/v1/proxy/routes/{route_id}/redirect-rules/{rule_id}`: Update a redirect rule for a route you own (https://www.coritan.com/docs/api/reference/client/reverse-proxy/#op-patch-api-v1-proxy-routes-route-id-redirect-rules-rule-id)
- `DELETE /api/v1/proxy/routes/{route_id}/redirect-rules/{rule_id}`: Delete a redirect rule for a route you own (https://www.coritan.com/docs/api/reference/client/reverse-proxy/#op-delete-api-v1-proxy-routes-route-id-redirect-rules-rule-id)
- `GET /api/v1/proxy/routes/{route_id}`: Get details of a proxy route you own (https://www.coritan.com/docs/api/reference/client/reverse-proxy/#op-get-api-v1-proxy-routes-route-id)
- `PATCH /api/v1/proxy/routes/{route_id}`: Update a proxy route you own (https://www.coritan.com/docs/api/reference/client/reverse-proxy/#op-patch-api-v1-proxy-routes-route-id)
