---
title: "Routes"
url: "/reference/resources/routes/"
kind: "reference"
summary: "Field reference for route configurations: virtual hosts, route matching, actions, retry policies and header mutations."
---

> Arion site index: [/llms.txt](/llms.txt)

# Routes

Field reference for route configurations: virtual hosts, route matching, actions, retry policies and header mutations.

A route configuration is written inline under the HTTP connection manager's `route_config` (or fetched over ADS with `rds`); the fields below are the Envoy field names Arion accepts, and any other field makes it refuse the configuration.

## route_config

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `name` | string | yes | — | Route configuration name. |
| `virtual_hosts` | list | yes | — | At least one [virtual host](#virtual-hosts). |
| `request_headers_to_add` | list | no | `[]` | [Header mutations](#header-mutations) applied to every request. |
| `request_headers_to_remove` | list | no | `[]` | Header names removed from every request. |
| `response_headers_to_add` | list | no | `[]` | Header mutations applied to every response. |
| `response_headers_to_remove` | list | no | `[]` | Header names removed from every response. |
| `most_specific_header_mutations_wins` | bool | no | `false` | Apply route-level mutations after virtual-host and global ones instead of before. |

## Virtual hosts

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `name` | string | yes | — | Virtual host name. |
| `domains` | list | yes | — | Host names to match: exact (`api.example.com`), suffix (`*.example.com`), prefix (`example.*`) or `*`. One wildcard, only at the start or end. Exact beats suffix, suffix beats prefix, `*` matches last; longer matches win within a kind. |
| `routes` | list | no | `[]` | [Routes](#routes), evaluated in order; the first match wins. |
| `retry_policy` | object | no | — | Default [retry policy](#retry-policy) for routes in this host. |
| `request_headers_to_add` / `request_headers_to_remove` | list | no | `[]` | Request header mutations for this host. |
| `response_headers_to_add` / `response_headers_to_remove` | list | no | `[]` | Response header mutations for this host. |

## Routes

Each route has a `match` and exactly one action: `route`, `redirect` or `direct_response`.

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `name` | string | no | `""` | Route name; rendered by the `%ROUTE_NAME%` access-log operator. |
| `match` | object | yes | — | [Match criteria](#route-match). |
| `route` | object | one of | — | Forward to a cluster ([route action](#route-action)). |
| `redirect` | object | one of | — | Return an HTTP [redirect](#redirect). |
| `direct_response` | object | one of | — | Return a fixed [response](#direct-response). |
| `typed_per_filter_config` | map | no | `{}` | [Per-route filter overrides](#typed-per-filter-config), keyed by filter name. |
| `request_headers_to_add` / `request_headers_to_remove` | list | no | `[]` | Request header mutations for this route. |
| `response_headers_to_add` / `response_headers_to_remove` | list | no | `[]` | Response header mutations for this route. |

## Route match

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `prefix` | string | one of | — | Path prefix match. |
| `path` | string | one of | — | Exact path match. |
| `safe_regex` | object | one of | — | `{regex: "..."}`; must match the full path. |
| `path_separated_prefix` | string | one of | — | Matches `/api`, `/api/` and `/api/...` but not `/apiv2`. Must not end in `/` or contain `?`. |
| `case_sensitive` | bool | no | `true` | Case sensitivity for `prefix`, `path` and `path_separated_prefix` (not regex). |
| `headers` | list | no | `[]` | [Header matchers](#header-matchers); all must match. |
| `query_parameters` | list | no | `[]` | [Query parameter matchers](#query-parameter-matchers); all must match. |

If no path specifier is given, the route matches every path.

```yaml,arion=fragment
routes:
  - match:
      prefix: "/api"
      headers:
        - name: ":method"
          string_match: { exact: "GET" }
      query_parameters:
        - name: version
          string_match: { exact: "v1" }
    route:
      cluster: api
```

### Header matchers

`name` may be a regular header or one of the pseudo-headers `:method`, `:path`, `:scheme`.

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `name` | string | yes | — | Header (or pseudo-header) to inspect. |
| `string_match` | object | one of | — | One of `exact`, `prefix`, `suffix`, `contains` or `safe_regex`, plus optional `ignore_case` (bool). |
| `exact_match` / `prefix_match` / `suffix_match` / `contains_match` | string | one of | — | Shorthand forms (case sensitive). |
| `safe_regex_match` | object | one of | — | `{regex: "..."}`. |
| `present_match` | bool | one of | — | Match when the header exists (any value). Combine with `invert_match` for "absent". |
| `invert_match` | bool | no | `false` | Invert the result. A missing header only inverts for `present_match`. |
| `treat_missing_header_as_empty` | bool | no | `false` | Evaluate a missing header as `""`. Ignored for `present_match`. |

`range_match` is not supported.

### Query parameter matchers

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `name` | string | yes | — | Query parameter name (case sensitive; first occurrence wins). |
| `string_match` | object | no | — | Same shape as the header `string_match`. |
| `present_match` | bool | no | `true` | Match on presence or absence. Used when `string_match` is not set. |

## Route action

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `cluster` | string | one of | — | Name of the target [cluster](@/reference/resources/clusters.md). |
| `weighted_clusters` | object | one of | — | Weighted split (below). |
| `cluster_header` | string | one of | — | Take the cluster name from this request header. |
| `timeout` | duration | no | `15s` | Whole-request upstream timeout, including retries. `0s` disables it. |
| `cluster_not_found_response_code` | enum | no | `SERVICE_UNAVAILABLE` | Also `NOT_FOUND` or `INTERNAL_SERVER_ERROR`. |
| `prefix_rewrite` | string | no | — | Replace the matched prefix before forwarding. Mutually exclusive with `regex_rewrite`. |
| `regex_rewrite` | object | no | — | `{pattern: {regex: "..."}, substitution: "..."}` applied to the path. |
| `retry_policy` | object | no | — | Route-level [retry policy](#retry-policy); overrides the virtual host's. |
| `priority` | enum | no | `DEFAULT` | Routing priority (`DEFAULT` or `HIGH`); selects the matching [circuit-breaker thresholds](@/reference/resources/clusters.md#circuit-breakers). |
| `hash_policy` | list | no | `[]` | [Hash policies](#hash-policy) for `RING_HASH` / `MAGLEV` clusters. |
| `upgrade_configs` | list | no | `[]` | `{upgrade_type, enabled}` entries. Only `websocket` can be enabled; `connect` is accepted only with `enabled: false`. |

### weighted_clusters

Traffic is split in proportion to the weights.

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `clusters` | list | yes | — | One entry per target. |
| `clusters[].name` | string | yes | — | Cluster name. |
| `clusters[].weight` | uint32 | yes | — | Relative weight, at least 1. |

```yaml,arion=fragment
route:
  weighted_clusters:
    clusters:
      - name: backend-v1
        weight: 90
      - name: backend-v2
        weight: 10
```

### hash_policy

Policies are applied in order and their hashes combined; a matching `terminal` policy stops the list, and if nothing matches a random hash is used.

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `header` | object | one of | — | `{header_name: "..."}` — hash that header's value. |
| `query_parameter` | object | one of | — | `{name: "..."}` — hash that query parameter's value. |
| `connection_properties` | object | one of | — | `{source_ip: true}` — hash the source address. |
| `terminal` | bool | no | `false` | Stop evaluating further policies when this one applies. |

## Retry policy

Available on virtual hosts and route actions.

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `retry_on` | string | yes | — | Comma-separated conditions: `5xx`, `gateway-error`, `reset`, `connect-failure`, `envoy-ratelimited`, `retriable-4xx`, `refused-stream`, `retriable-status-codes`, `retriable-headers`, `http3-post-connect-failure`. |
| `num_retries` | uint32 | no | `1` | Maximum retry attempts. |
| `per_try_timeout` | duration | no | none | Timeout per attempt. Unset means attempts are bounded only by the route `timeout`. |
| `retriable_status_codes` | list | no | `[]` | Status codes for `retriable-status-codes`. |
| `retry_back_off` | object | no | `25ms`/`250ms` | `{base_interval, max_interval}` for exponential backoff. `base_interval` is rounded up to at least 1ms; `max_interval` defaults to 10x the base and must not be smaller than it. |
| `retriable_headers` | list | no | `[]` | [Header matchers](#header-matchers) against the response, for `retriable-headers`. |
| `retriable_request_headers` | list | no | `[]` | Request headers that must be present for retries to be attempted. |

```yaml,arion=fragment
route:
  cluster: api
  timeout: 10s
  retry_policy:
    retry_on: "5xx,reset"
    num_retries: 3
    per_try_timeout: 2s
    retry_back_off:
      base_interval: 0.05s
      max_interval: 0.5s
```

## Redirect

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `response_code` | enum | no | `MOVED_PERMANENTLY` | Also `FOUND`, `SEE_OTHER`, `TEMPORARY_REDIRECT`, `PERMANENT_REDIRECT`. |
| `host_redirect` | string | no | — | Replace the host. |
| `port_redirect` | uint16 | no | — | Replace the port. Combined with `host_redirect` they must form a valid authority. |
| `scheme_redirect` | string | one of | — | Replace the scheme (for example `https`). |
| `https_redirect` | bool | one of | — | Shorthand for redirecting to `https`. |
| `path_redirect` | string | one of | — | Replace the whole path. |
| `prefix_rewrite` | string | one of | — | Replace the matched prefix in the redirect target. |
| `regex_rewrite` | object | one of | — | `{pattern: {regex}, substitution}` applied to the path. |
| `strip_query` | bool | no | `false` | Drop the query string from the redirect target. |

## Direct response

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `status` | uint32 | yes | — | HTTP status code to return. |
| `body` | object | no | empty | A [data source](@/reference/resources/secrets.md#data-sources) (`inline_string`, `inline_bytes`, `filename` or `environment_variable`), read once at configuration load. |

```yaml,arion=fragment
routes:
  - match:
      prefix: "/healthz"
    direct_response:
      status: 200
      body:
        inline_string: "ok"
```

## Header mutations

`*_headers_to_add` entries wrap a header and an append behavior; `*_headers_to_remove` entries are plain header names.

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `header.key` | string | yes | — | Header name. |
| `header.value` | string | one of | — | Header value. Supports [access-log command operators](@/reference/observability/access-log-operators.md) such as `%REQ(:PATH)%` or `%DOWNSTREAM_REMOTE_ADDRESS%`. |
| `header.raw_value` | bytes | one of | — | Raw value alternative to `value`; setting both is an error. |
| `append_action` | enum | no | `APPEND_IF_EXISTS_OR_ADD` | Also `ADD_IF_ABSENT`, `OVERWRITE_IF_EXISTS_OR_ADD`, `OVERWRITE_IF_EXISTS`. |
| `keep_empty_value` | bool | no | `false` | Keep the header when its rendered value is empty. |

```yaml,arion=fragment
request_headers_to_add:
  - header:
      key: x-original-path
      value: "%REQ(:PATH)%"
    append_action: OVERWRITE_IF_EXISTS_OR_ADD
request_headers_to_remove:
  - x-internal-debug
```

## typed_per_filter_config

A map from the `name` of a filter in `http_filters` to an override; the named filter must exist and the override type must match its type.

| `@type` (under `type.googleapis.com/`) | Overrides | Description |
|---|---|---|
| `envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit` | [local rate limit](@/reference/filters/http/local-rate-limit.md) | Replaces the filter's settings on this route. |
| `envoy.extensions.filters.http.rbac.v3.RBACPerRoute` | [HTTP RBAC](@/reference/filters/http/rbac.md) | `{rbac: ...}`; an empty override disables RBAC for the route. |
| `envoy.extensions.filters.http.ext_proc.v3.ExtProcPerRoute` | [external processing](@/reference/filters/http/ext-proc.md) | Disables or overrides ext_proc on this route. |
| `envoy.config.route.v3.FilterConfig` | any of the above | Wrapper with `disabled: true` to switch a filter off for the route, or `config` carrying one of the overrides. `is_optional` is not supported. |
