Leaf nodes let you extend your NATS cluster to edge locations without the complexity of a full supercluster.
Here’s a NATS cluster with leaf nodes in action. Imagine we have a central NATS cluster running on nats-1:4222, nats-2:4222, and nats-3:4222. This is our "core" cluster.
At a remote site, say, a factory floor, we have a single NATS server acting as a leaf node, running on leaf-factory:4222. This leaf node needs to know how to connect to the core cluster.
In the leaf node’s configuration file (leaf.conf):
listen: 0.0.0.0:4222
# This is the crucial part: defining the connections to the core cluster
# The leaf node will try to connect to *any* of these URLs.
# If one fails, it tries the next.
# The core cluster servers should be configured to accept connections from leaf nodes.
jetstream: {
storeDir: "./store"
}
leaf {
remotes = [
{ url: "nats://nats-1:4222" },
{ url: "nats://nats-2:4222" },
{ url: "nats://nats-3:4222" }
]
}
On the core cluster side, the servers (nats-1, nats-2, nats-3) need to be configured to allow leaf node connections. This is done using the authorization block in their configuration, specifically by allowing connections from the IP address or CIDR range of the factory leaf node.
For example, in core.conf on nats-1:
listen: 0.0.0.0:4222
cluster {
listen: 0.0.0.0:6222
routes = [
"nats://nats-2:6222",
"nats://nats-3:6222"
]
}
jetstream {
storeDir: "./store"
}
authorization {
# Allow connections from the leaf node's IP address.
# If the leaf node is on a different network segment, you'd use its CIDR.
# For simplicity, let's assume it's on a known subnet.
# Replace with the actual IP or CIDR of your factory leaf node.
allowed_leaf_cidrs = ["192.168.1.0/24"]
}
Now, a client connected to leaf-factory:4222 can publish a message to a subject like factory.orders.new. This message will be routed by the leaf node to the core cluster. If a service subscribed to factory.orders.new is running on the core cluster, it will receive the message.
Conversely, if a service on the core cluster publishes a message to factory.alerts.critical, the core cluster will route this message to the leaf-factory:4222 leaf node, and any clients subscribed to factory.alerts.critical on that leaf node will receive it.
The problem leaf nodes solve is scaling NATS to geographically distributed environments where maintaining a full, interconnected supercluster would be network-intensive and complex. Leaf nodes act as gateways, synchronizing with a core cluster and handling local client connections. They can operate autonomously for a period if connectivity to the core is lost, buffering messages locally (if JetStream is enabled) and synchronizing upon reconnection. This provides resilience and performance for edge deployments.
Internally, a leaf node establishes a persistent WebSocket connection (or plain TCP if configured) to one of the remote URIs specified in its configuration. Once connected, it registers itself with the core cluster. The core cluster then knows about the leaf node and can route messages to it. When the leaf node receives a message from a local client destined for a subject that the core cluster is interested in (e.g., because a subscriber exists there), it forwards the message. Similarly, messages published on the core that match subjects routed to the leaf node are sent down to it.
The exact levers you control are the remotes configuration in the leaf node, defining which core cluster endpoints it should try to connect to. On the core side, it’s the allowed_leaf_cidrs or explicit IP configurations within the authorization block that dictate which leaf nodes are permitted to connect. You also control the subjects that are routed between the core and the leaf; NATS routing is subject-based, so if no subscribers or publishers are active for a given subject prefix on one side, messages for that prefix won’t be actively routed back and forth.
A common misconception is that leaf nodes require explicit configuration on the core cluster for each leaf. This isn’t true. The core cluster’s authorization block is sufficient to permit connections from a range of IPs or specific IPs. The leaf node initiates the connection, and the core accepts or rejects it based on the authorization rules. This makes management significantly simpler for large numbers of edge locations.
The next thing you’ll likely encounter is how to manage JetStream streams and consumers across leaf and core clusters, specifically regarding message ordering and fault tolerance when the leaf node is temporarily disconnected from the core.