# Super-clusters

Acme's `east` cluster is healthy: three servers form a full mesh of routes, with the `ORDERS` stream replicated across them. But half of Acme's customers are a continent away from `east`, and every order they place has to cross the continent to reach it. So Acme brings up a second region — a `west` data center near those customers, where their orders land with far less latency.

Now two regions have to behave as one system — a publisher in either reaches a subscriber in the other — without dragging every message across the slow link between them.

You could run `west` as a completely separate NATS deployment. But then a publisher in `west` could never reach a subscriber in `east`, and the `ORDERS` stream in one region would be invisible to the other, leaving you with two isolated islands.

You could also stretch one cluster across both regions, putting all six servers in a single mesh of routes. That works, but every server holds a route to every other server, and route traffic flows freely across the slow, expensive link between regions. A full mesh assumes the members are close together.

A **super-cluster** is the shape for this. It joins two independent clusters into one logical system without combining them into a single mesh. This page wires `east` and `west` together and shows how traffic stays in its home region by default.

## Two concepts on this page

This page introduces two ideas:

* **Gateways**: the cluster-to-cluster connection that joins clusters into a super-cluster.
* **Geo-affinity**: the behavior that keeps queue-group and request traffic in its home region, crossing a gateway only when no local worker can serve it.

## How a gateway joins clusters

Inside a cluster, every server holds a **route** to every other server, the full mesh from the [previous page](/learn/topologies/your-first-cluster.md). A **gateway** is a different kind of connection. It joins one *cluster* to another *cluster*.

A route ties two *servers* together and assumes they're close; a gateway ties two *clusters* together and assumes they're far apart. Stretch one cluster across both regions and every server holds a route to every server in the other region — a full mesh running over the slow link. A super-cluster keeps each region's mesh local and connects the regions through gateways instead, so the servers never mesh across the WAN, however many each region runs. Each server opens just one gateway connection to each *other cluster*, never one to every remote server.

A gateway also carries less. A route propagates interest freely inside its cluster; a gateway forwards a message to another cluster only when that cluster has a subscriber for it, and prefers a local worker over a remote one. Those two behaviors — interest-based forwarding and geo-affinity — get their own sections below, and they're what keep the inter-region link quiet.

A super-cluster (sometimes called a cluster of clusters, the one time this guide will write that phrase) is just clusters joined this way. Each cluster keeps running on its own. The gateway is the connection between them.

**Message flow — superClusterAnimated:** Interactive NATS flow diagram.

* n1-east → n3-east
* n2-east → n3-east
* n1-west → n3-west
* n2-west → n3-west
* order-svc → n2-east (subject: orders.created)
* n2-east → n1-east
* n1-east → n1-west (subject: gateway (east ↔ west))
* n1-west → n2-west
* n2-west → warehouse

## Gateways carry only what has interest

A gateway doesn't blindly forward every message to the other side. It carries a message across only when the remote cluster has a subscriber interested in that subject.

This is the whole reason gateways are cheap to run across a slow link. A publisher in `east` floods `orders.created` thousands of times a second. If nobody in `west` subscribes to `orders.created`, not one of those messages crosses the gateway, because there is no interest on the far side to forward them to.

The wire-level detail of how gateways advertise and track this interest lives in [Reference → Gateway protocol](/reference/protocols/gateway.md). All you need here is the behavior: no interest on the far side means no traffic across the gateway.

## Wiring east to west

You configure a gateway with a `gateway {}` block. The block names the local cluster and lists the remote clusters to reach.

Here's the `gateway {}` block for the `east` servers. It declares the local gateway name `east`, the port this server listens on for inbound gateway connections, and a `gateways` array pointing at `west`:

```
# east gateway block — the name and the gateways list are shared

# by n1-east, n2-east, n3-east; each server picks its own port.

gateway {

  name: "east"

  port: 7222



  gateways: [

    { name: "west", urls: ["nats://127.0.0.1:7322", "nats://127.0.0.1:7323", "nats://127.0.0.1:7324"] }

  ]

}
```

Two things to read carefully.

The `name` field identifies the *cluster*, not the server. Every server in `east` uses the identical gateway name `east`. It has to match the name in this server's `cluster {}` block exactly — set them differently and the server refuses to start (`cluster name conflicts between cluster and gateway definitions`). A server's own entry in the `gateways` array is ignored automatically, so the `name` and the `gateways` list are identical across all three `east` servers. Only the `port` each one listens on differs.

The `gateways` array lists every remote cluster, each with its `name` and the `urls` to reach its gateway listeners. Listing all three `west` URLs gives the connection somewhere to land if one `west` server is down. That's also the hint that each remote server runs its own gateway listener on its own port. The `port` line above is per server: in a real deployment `n1-east`, `n2-east`, and `n3-east` each bind a distinct gateway port, and the three `west` URLs point at three distinct `west` listeners. The shared part is the `name` and the `gateways` list, not the port.

You don't have to list every cluster exhaustively, though. Like routes, gateways **gossip**: point a server at a few remote gateways and it discovers the rest, including clusters it was never told about directly. Seed `east` with `west` and `west` with a third region, and `east` picks up that region on its own. The URLs are a starting point for discovery and a fallback if one is down, not a list you must keep complete.

The `west` servers get the mirror-image block, with local name `west` pointing back at the `east` gateway URLs:

```
# west gateway block — name and gateways list shared by

# n1-west, n2-west, n3-west; each server picks its own port.

gateway {

  name: "west"

  port: 7322



  gateways: [

    { name: "east", urls: ["nats://127.0.0.1:7222", "nats://127.0.0.1:7223", "nats://127.0.0.1:7224"] }

  ]

}
```

Each cluster keeps the `cluster {}` block and the client `port` it had before. The `gateway {}` block is additive. You're not rebuilding `east`; you're adding a gateway connection from it to `west`.

To stand the whole thing up on your own machine without hand-editing six files, this script writes every config and starts all six servers. East uses client ports 4222-4224 and west 4225-4227; each server's gateway `name` matches its cluster `name`, as above.

```
# Write a cluster+gateway config for each server, then start the super-cluster.

write_conf() {  # name cluster client cluster-port route gateway-port remote remote-urls

  cat > "$1.conf" <<EOF

server_name: $1

listen: 127.0.0.1:$3

cluster {

  name: $2

  listen: 127.0.0.1:$4

  routes: [ nats://127.0.0.1:$5 ]

}

gateway {

  name: $2

  listen: 127.0.0.1:$6

  gateways: [ { name: $7, urls: [ $8 ] } ]

}

EOF

}



EAST_URLS='nats://127.0.0.1:7222, nats://127.0.0.1:7223, nats://127.0.0.1:7224'

WEST_URLS='nats://127.0.0.1:7322, nats://127.0.0.1:7323, nats://127.0.0.1:7324'



write_conf n1-east east 4222 6222 6223 7222 west "$WEST_URLS"

write_conf n2-east east 4223 6223 6222 7223 west "$WEST_URLS"

write_conf n3-east east 4224 6224 6222 7224 west "$WEST_URLS"

write_conf n1-west west 4225 6225 6226 7322 east "$EAST_URLS"

write_conf n2-west west 4226 6226 6225 7323 east "$EAST_URLS"

write_conf n3-west west 4227 6227 6225 7324 east "$EAST_URLS"



for s in n1-east n2-east n3-east n1-west n2-west n3-west; do

  nats-server -c "$s.conf" &

done
```

Stop everything later with `pkill nats-server`. These configs leave JetStream off to keep the gateway demo simple — add the `jetstream` block from the [previous page](/learn/topologies/jetstream-in-a-cluster.md) if you want `ORDERS` in the mix too.

## Confirm the super-cluster formed

A super-cluster is only real if a message crosses it. Subscribe in `west`, publish in `east`, and watch the order arrive on the far side of the gateway.

Subscribe to `orders.>` on a `west` server, in its own terminal:

```
nats sub "orders.>" --server nats://localhost:4225
```

In another terminal, publish to an `east` server:

```
nats pub orders.created "order ord_8w2k" --server nats://localhost:4222
```

The `west` subscriber prints the order:

```
[#1] Received on "orders.created"

order ord_8w2k
```

It was published in `east`, crossed the gateway, and arrived in `west`. That only happens once the gateway is live *and* `west` has advertised interest in `orders.>` across it — the interest-based forwarding from [earlier](#gateways-carry-only-what-has-interest). If nothing arrives, the gateway never formed: check that each server's gateway `name` matches its cluster `name`.

## Geo-affinity keeps traffic local

Now the second concept. Acme runs the same fleet of order workers in both regions: a **queue group** named `order-workers`, where each message goes to exactly one worker in the group. (If queue groups are hazy, the [queue-groups primer](/concepts/queue-groups.md) is the five-minute recap.)

Here's the question a super-cluster has to answer: when a worker exists in *both* `east` and `west`, and an order is published in `east`, which worker handles it?

The answer is **geo-affinity**. NATS prefers a local queue subscriber first. An order published in `east` goes to an `east` worker, even though a `west` worker is also subscribed and willing. The message never crosses the gateway, because it doesn't need to.

This keeps the slow inter-region link quiet. Day to day, `east` orders are served in `east` and `west` orders in `west`, so each region's traffic stays within that region.

The gateway carries the message only when the local side can't serve. If every `east` worker is down, an order published in `east` has no local subscriber, so geo-affinity falls through and the message crosses the gateway to a remote cluster that has an interested worker. When more than one remote cluster has a worker for the queue group, NATS forwards across every gateway with interest. The queue-group rule still applies on the far side, so the order reaches exactly one worker, and no work is lost in the process.

You can watch this local-first behavior. Start a queue worker in each region, each in its own terminal:

```
# terminal 1 — a worker in east

nats sub orders.created --queue order-workers --server nats://localhost:4222
```

```
# terminal 2 — a worker in west

nats sub orders.created --queue order-workers --server nats://localhost:4225
```

Publish an order in `east`:

```
nats pub orders.created "order ord_8w2k" --server nats://localhost:4222
```

The `east` worker prints it and `west` stays silent — geo-affinity served it locally. Stop the `east` worker (`Ctrl+C`) and publish again: now `west` prints it. With no local worker left, the order fell through to the gateway and crossed to `west`, and nothing on the publisher changed to make it happen.

## Pitfalls

A super-cluster has a few traps that all come from one habit: thinking about it like a bigger cluster, which it is not. The connection between regions is a gateway, and gateways behave differently from routes.

**Reaching for routes to span regions.** A route is the intra-cluster link that builds the full mesh. It assumes its peers are close and floods freely. Stretch one `cluster {}` across `east` and `west` and every server holds a route to every other server, with cluster traffic crossing the slow link constantly. Don't grow a cluster across regions. Join two independent clusters with a `gateway {}` block instead, so only interested traffic crosses the gateway.

**Mismatched gateway names.** Each entry in the `gateways` array must use the remote cluster's exact name. Typo it — `wset` for `west` — and the server keeps dialing a cluster that never answers to that name (`Failing connection to gateway "wset", remote gateway name is "west"`), and the gateway never forms. Nothing fails at startup, so confirm with the cross-region publish from [Confirm the super-cluster formed](#confirm-the-super-cluster-formed): if the order never reaches the other side, a name is wrong.

**Chatty cross-region traffic without local workers.** Geo-affinity only keeps traffic home when a *local* queue subscriber exists to serve it. Run all your `order-workers` in `east` and let `west` clients publish, and every `west` order crosses the gateway to reach a worker. The slow link carries your steady-state load, not just failover. Don't centralize workers in one region and assume geo-affinity avoids the cross-region cost. Place a queue subscriber for each workload in every region that produces that work, so each region serves its own orders and the gateway stays quiet.

## Where you are

Acme's deployment grew from one cluster to a super-cluster:

* `east` and `west` are independent clusters, each a full mesh of routes.
* A **gateway** joins them, carrying only traffic with interest on the far side.
* **Geo-affinity** keeps queue-group and request traffic in its home region, crossing the gateway only when the local side can't serve.
* Nothing about publishing or subscribing changed — the same `orders.created` publish still reaches an interested subscriber, now even across regions.

## What's next

The next page pushes NATS past the data center entirely. A [leaf node](/learn/topologies/leaf-nodes.md) is a server that *dials out* to the `east` cluster — a single outbound connection, so it works from a factory or a laptop with no inbound ports open. That one connection is two-way, so once it's up the leaf bridges order traffic in both directions.

## See also

* [Reference → Gateway protocol](/reference/protocols/gateway.md) — the wire-level detail of how gateways advertise interest and connect.
* [Core Concepts → Queue Groups](/concepts/queue-groups.md) — the recap of the queue-group behavior geo-affinity steers.
* [Operate → Clustering & Replication](/learn/clustering/.md) — how a stream's replicas work within one cluster (a gateway carries interest across regions, not stream copies).
