MQTT clustering for high availability is less about replicating messages and more about distributing the connection load and session state so that if one broker node dies, clients can reconnect to another without losing their subscriptions or QoS 1/2 messages.
Let’s look at a common setup using VerneMQ, a popular distributed MQTT broker. Imagine we have two nodes, vernemq-1 and vernemq-2, on separate machines.
Here’s a basic VerneMQ configuration for clustering:
# vernemq.conf on vernemq-1
cluster {
name = my_mqtt_cluster
nodename = vernemq@vernemq-1.example.com
listener {
tcp = 1883
ws = 8080
}
plugins = mq_acl_file, mq_auth_anonymous
# This node will try to join the cluster by discovering nodes with this name
# If other nodes are already part of the cluster, this node will join them.
# If no other nodes are found, this node will start its own cluster.
discovery.type = etcd
discovery.etcd.hosts = 192.168.1.100:2379, 192.168.1.101:2379
discovery.etcd.path = /vernemq/cluster/my_mqtt_cluster
}
And on vernemq-2:
# vernemq.conf on vernemq-2
cluster {
name = my_mqtt_cluster
nodename = vernemq@vernemq-2.example.com
listener {
tcp = 1883
ws = 8080
}
plugins = mq_acl_file, mq_auth_anonymous
discovery.type = etcd
discovery.etcd.hosts = 192.168.1.100:2379, 192.168.1.101:2379
discovery.etcd.path = /vernemq/cluster/my_mqtt_cluster
}
In this setup, both nodes are configured to use etcd for discovery. When VerneMQ starts, it registers itself in etcd under the specified discovery.etcd.path. Other nodes looking for the my_mqtt_cluster will find the registered nodes in etcd and automatically join the cluster.
Now, let’s see it in action. A client connects to vernemq-1 on port 1883.
# On a client machine
mosquitto_pub -h vernemq-1.example.com -t "sensors/temperature" -m "25" -q 1
VerneMQ receives this message. Because it’s clustered, it doesn’t just store the message locally. It uses a distributed hash table (DHT) to determine which node is responsible for the sensors/temperature topic’s session. If vernemq-1 is responsible, it handles the message. If vernemq-2 is responsible (e.g., if the topic was initially subscribed to there), vernemq-1 will forward the message to vernemq-2.
When another client subscribes to the same topic, it can connect to either vernemq-1 or vernemq-2.
# On another client machine
mosquitto_sub -h vernemq-2.example.com -t "sensors/temperature" -v
If this client connects to vernemq-2, VerneMQ checks its DHT. It finds that sensors/temperature is associated with a session, and it retrieves that session’s subscription information, potentially from vernemq-1 if that’s where it originated. vernemq-2 then establishes the subscription and starts receiving messages for that topic, even if the publisher connected to a different node.
The core problem this solves is stateful availability. Without clustering, if vernemq-1 goes down, any client connected to it is disconnected. If that client was holding a persistent session, it would need to re-establish it, and any QoS 1 or 2 messages sent while it was down would be lost unless the publisher also had retry logic. In a cluster, the client can simply reconnect to vernemq-2. VerneMQ on vernemq-2 will detect the existing session via the cluster’s shared state and resume service without the client needing to re-authenticate or re-subscribe, and without message loss.
To inspect the cluster status, you can use the vmq-admin tool. On either node, run:
./bin/vmq-admin cluster nodes
This will show you all nodes currently participating in the cluster. You should see vernemq@vernemq-1.example.com and vernemq@vernemq-2.example.com listed.
To see which node is handling which client session, you can use:
./bin/vmq-admin session list
This command helps diagnose where client state is being managed. If vernemq-1 were to fail, vmq-admin cluster nodes on vernemq-2 would eventually reflect only vernemq-2, and any client reconnecting to vernemq-2 would have its session seamlessly transferred or re-established from the cluster’s shared state.
The surprising thing is that messages aren’t typically replicated across all nodes for every publish. Instead, the cluster focuses on replicating session state and subscription information. This is far more efficient. When a node receives a publish for a topic, it consults its internal routing information (derived from the DHT) to determine which node is the owner of that topic’s session. It then forwards the message directly to that owner node. If the owner node is down, the message might be temporarily queued or dropped depending on configuration, but the critical part is that the session is what’s resiliently managed.
The next step is usually configuring load balancing in front of the cluster and understanding how different QoS levels interact with clustered session management.