---
title: "Clusters"
url: "/reference/resources/clusters/"
kind: "reference"
summary: "Field reference for clusters: discovery types, load balancing, endpoints, health checks, circuit breakers, protocol options and upstream TLS."
---

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

# Clusters

Field reference for clusters: discovery types, load balancing, endpoints, health checks, circuit breakers, protocol options and upstream TLS.

Clusters are declared under `envoy_bootstrap.static_resources.clusters` (or delivered over CDS) using standard Envoy field names; fields outside this reference make Arion refuse the configuration.

## cluster

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `name` | string | yes | — | Unique cluster name; routes reference it. |
| `type` | enum | yes | — | `STATIC`, `STRICT_DNS`, `EDS` or `ORIGINAL_DST`. `LOGICAL_DNS` is not supported. |
| `load_assignment` | object | see below | — | [Endpoints](#load-assignment). Required for `STATIC` (IP, pipe or internal addresses) and `STRICT_DNS` (host names resolved over DNS); forbidden for `EDS` (endpoints arrive over ADS, see [xDS resource types](@/reference/xds/resource-types.md)) and ignored for `ORIGINAL_DST`. |
| `lb_policy` | enum | no | `ROUND_ROBIN` | `ROUND_ROBIN`, `RANDOM`, `LEAST_REQUEST`, `RING_HASH`, `MAGLEV` or `CLUSTER_PROVIDED`. `RING_HASH` and `MAGLEV` hash with the route's `hash_policy`; `ORIGINAL_DST` clusters must use `CLUSTER_PROVIDED`. |
| `load_balancing_policy` | object | no | — | Typed alternative to `lb_policy`: exactly one policy whose `typed_config` is one of the six accepted `@type`s (below). |
| `connect_timeout` | duration | no | none | Upstream connection establishment timeout. |
| `cleanup_interval` | duration | no | `5s` | How long an idle `ORIGINAL_DST` host is kept before it is expired. |
| `health_checks` | list | no | `[]` | At most one [health check](#health-checks). |
| `circuit_breakers` | object | no | defaults | [Thresholds](#circuit-breakers) per routing priority. |
| `typed_extension_protocol_options` | map | no | HTTP/1.1 | One entry of [HTTP protocol options](#http-protocol-options). |
| `transport_socket` | object | no | plaintext | [Upstream TLS](#upstream-tls-upstreamtlscontext), `RawBuffer`, or PROXY protocol [upstream transport](#proxy-protocol-upstream-transport). |
| `upstream_bind_config` | object | no | — | Only `socket_options` with a single `SO_BINDTODEVICE` entry (`level: 1`, `name: 25`). |
| `original_dst_lb_config` | object | no | — | [Original-destination routing](#original-destination) settings; only valid with `type: ORIGINAL_DST`. |

`load_balancing_policy` accepts these `typed_config` types (all under `type.googleapis.com/envoy.extensions.load_balancing_policies.`): `round_robin.v3.RoundRobin`, `random.v3.Random`, `least_request.v3.LeastRequest`, `ring_hash.v3.RingHash`, `maglev.v3.Maglev` and `override_host.v3.OverrideHost` — each with an empty config, except `OverrideHost` which takes exactly one `override_host_sources` entry with `header: <name>` plus a required `fallback_policy` (any of the other five).

```yaml,arion=fragment
clusters:
  - name: api
    type: STATIC
    lb_policy: ROUND_ROBIN
    connect_timeout: 0.25s
    load_assignment:
      endpoints:
        - lb_endpoints:
            - endpoint:
                address:
                  socket_address: { address: 127.0.0.1, port_value: 5678 }
```

## load_assignment

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `cluster_name` | string | no | — | Informational; the enclosing cluster's `name` is what routes use. |
| `endpoints` | list | yes | — | Endpoint groups. `locality` metadata is not supported inside a group. |
| `endpoints[].priority` | uint32 | no | `0` | Failover priority. Priorities used across groups must run 0..N without gaps; priority 0 serves first and traffic spills to the next priority as its hosts become unhealthy. |
| `endpoints[].lb_endpoints` | list | yes | — | The endpoints of the group. |
| `lb_endpoints[].endpoint.address` | object | yes | — | `socket_address` (`address`, `port_value`), `pipe` (`path`) or `envoy_internal_address` (`server_listener_name`). |
| `lb_endpoints[].load_balancing_weight` | uint32 | no | `1` | Relative weight, at least 1. |
| `lb_endpoints[].health_status` | enum | no | `HEALTHY` | Initial health (`HEALTHY`, `UNHEALTHY`; `UNKNOWN` counts as healthy). |

```yaml,arion=fragment
load_assignment:
  endpoints:
    - priority: 0
      lb_endpoints:
        - endpoint:
            address:
              socket_address: { address: 10.0.0.1, port_value: 8080 }
          load_balancing_weight: 3
        - endpoint:
            address:
              socket_address: { address: 10.0.0.2, port_value: 8080 }
          load_balancing_weight: 1
    - priority: 1
      lb_endpoints:
        - endpoint:
            address:
              socket_address: { address: 10.0.1.1, port_value: 8080 }
```

## Health checks

One entry under `health_checks`. The timing fields sit at the top level; the checker itself is one of `http_health_check`, `tcp_health_check` or `grpc_health_check`.

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `timeout` | duration | yes | — | Time to wait for a check response. |
| `interval` | duration | yes | — | Interval between checks. |
| `unhealthy_threshold` | uint16 | yes | — | Failures before a host is marked unhealthy. |
| `healthy_threshold` | uint16 | yes | — | Successes before a host is marked healthy again. |
| `initial_jitter` | duration | no | none | Random delay before the first check. |
| `interval_jitter` | duration | no | none | Random amount added to each interval. |
| `interval_jitter_percent` | float | no | `0` | Fraction of `interval` (0.0-1.0) added as jitter; combines with `interval_jitter`. |
| `unhealthy_interval` | duration | no | `interval` | Interval used while a host is unhealthy. |
| `unhealthy_edge_interval` | duration | no | `unhealthy_interval` | Interval for the first check after a host turns unhealthy. |
| `healthy_edge_interval` | duration | no | `interval` | Interval for the first check after a host turns healthy. |

### http_health_check

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `path` | string | no | none | Request path; must not contain a query string. |
| `host` | string | no | cluster name | Value of the Host header; the cluster name must be a valid authority if this is unset. |
| `method` | enum | no | `GET` | Only `GET` is supported. |
| `expected_statuses` | list | no | `[[200,201)]` | Status ranges (`start` inclusive, `end` exclusive, within 100-600) that count as healthy. |
| `retriable_statuses` | list | no | `[]` | Status ranges that do not immediately mark the host unhealthy. |
| `codec_client_type` | enum | no | `HTTP1` | `HTTP1` or `HTTP2`; must match the cluster's [protocol options](#http-protocol-options). |

### tcp_health_check

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `send` | object | no | connect only | Payload to send: `{text: "<hex>"}` (hex-encoded bytes) or `{binary: <bytes>}`. |
| `receive` | list | no | `[]` | Payloads that must appear in the response, same forms as `send`. |

### grpc_health_check

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `service_name` | string | no | `""` | Service name passed to the standard gRPC health service. The cluster must use HTTP/2 protocol options. |

```yaml,arion=fragment
health_checks:
  - timeout: 0.1s
    interval: 5s
    healthy_threshold: 3
    unhealthy_threshold: 2
    http_health_check:
      path: /health
```

## Circuit breakers

`circuit_breakers.thresholds` is a list; each entry applies to one routing `priority` and the last entry for a priority wins. Requests routed with `priority: HIGH` on the route action use the `HIGH` entry. When a limit trips, HTTP requests are rejected with 503 and `x-envoy-overloaded: true`.

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `priority` | enum | no | `DEFAULT` | `DEFAULT` or `HIGH`. |
| `max_connections` | uint32 | no | `1024` | Maximum concurrent upstream connections. |
| `max_requests` | uint32 | no | `1024` | Maximum concurrent in-flight requests. |
| `max_retries` | uint32 | no | `3` | Maximum concurrent retries. |
| `track_remaining` | bool | no | `false` | Track remaining capacity for observability. |

`max_pending_requests`, `retry_budget` and `per_host_thresholds` are not supported.

```yaml,arion=fragment
circuit_breakers:
  thresholds:
    - priority: DEFAULT
      max_requests: 50
      max_retries: 3
    - priority: HIGH
      max_requests: 100
      max_retries: 5
```

## HTTP protocol options

One entry under `typed_extension_protocol_options` with `@type: type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions`. Only `explicit_http_config` is supported (`auto_config` and `use_downstream_protocol_config` are not).

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `common_http_protocol_options.idle_timeout` | duration | no | none | Idle timeout for upstream connections. |
| `explicit_http_config.http_protocol_options` | object | one of | selected | Selects HTTP/1.1. Only `enable_trailers` (bool, default `false`) is accepted inside. |
| `explicit_http_config.http2_protocol_options` | object | one of | — | Selects HTTP/2; fields below. |

### http2_protocol_options

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `max_concurrent_streams` | uint32 | no | none | Maximum concurrent streams per connection. |
| `initial_stream_window_size` | uint32 | no | none | Per-stream flow-control window; must be greater than 0. |
| `initial_connection_window_size` | uint32 | no | none | Per-connection flow-control window; must be greater than 0. |
| `connection_keepalive.interval` | duration | yes* | — | PING keepalive interval (* required inside `connection_keepalive`). |
| `connection_keepalive.timeout` | duration | no | none | Time to wait for a PING response before closing the connection. |

```yaml,arion=fragment
typed_extension_protocol_options:
  envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
    "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
    explicit_http_config:
      http2_protocol_options: {}
```

## Upstream TLS (UpstreamTlsContext)

Set as the cluster `transport_socket` with `@type: type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext`.

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `sni` | string | yes | — | Server name sent in the TLS handshake and used to verify the upstream certificate. |
| `common_tls_context.tls_params` | object | no | min TLS 1.2 | Same [protocol bounds](@/reference/resources/listeners.md#tls-params) as downstream TLS. |
| `common_tls_context.tls_certificates` | list | no | none | At most one client certificate (`{certificate_chain, private_key}`) for mTLS. |
| `common_tls_context.tls_certificate_sds_secret_configs` | list | no | none | At most one named [secret](@/reference/resources/secrets.md) reference instead of an inline certificate. |
| `common_tls_context.validation_context` | object | no | — | `{trusted_ca, trust_chain_verification}` for verifying the upstream certificate. |
| `common_tls_context.validation_context_sds_secret_config` | object | no | — | Named reference to a validation-context secret. |

```yaml,arion=fragment
transport_socket:
  name: envoy.transport_sockets.tls
  typed_config:
    "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
    sni: api.example.com
    common_tls_context:
      validation_context:
        trusted_ca:
          filename: /etc/ssl/certs/ca-certificates.crt
```

### PROXY protocol upstream transport

`@type: type.googleapis.com/envoy.extensions.transport_sockets.proxy_protocol.v3.ProxyProtocolUpstreamTransport` wraps the upstream connection in a PROXY protocol header.

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `config.version` | enum | yes | — | `V1` or `V2`. |
| `config.pass_through_tlvs` | object | no | none | `match_type` (`INCLUDE_ALL` or `INCLUDE`) and `tlv_type` list — TLVs copied from the downstream PROXY header (v2). |
| `config.added_tlvs` | list | no | `[]` | `{type, value}` TLV entries to append (type 0-255). |
| `transport_socket` | object | no | plaintext | Inner transport: an `UpstreamTlsContext` or `RawBuffer`. |

## Original destination

`type: ORIGINAL_DST` clusters pick the upstream address per request instead of from `load_assignment`; `lb_policy` must be `CLUSTER_PROVIDED`. The routing method comes from `original_dst_lb_config`; with neither method configured, the request authority (Host) is used as the destination:

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `use_http_header` | bool | no | `false` | Take the destination from a request header. Mutually exclusive with `metadata_key`. |
| `http_header_name` | string | no | `x-envoy-original-dst-host` | Header carrying `host:port` when `use_http_header` is true. |
| `metadata_key` | object | no | — | `{key, path: [{key: ...}]}` — read the destination from request metadata set by an earlier filter. |
| `upstream_port_override` | uint16 | no | none | Replace the port of the resolved destination. |

```yaml,arion=fragment
clusters:
  - name: dynamic
    type: ORIGINAL_DST
    lb_policy: CLUSTER_PROVIDED
    connect_timeout: 5s
    original_dst_lb_config:
      use_http_header: true
```
