Cluster

Run one Minecraft network across several machines, joined with a single code.

One node is the leader and decides, the others are workers and execute.

For a step-by-step guide to adding a machine, see Adding a node.

Setting it up

The leader has to be reachable for the other machines. In its mutecloud.toml:

mutecloud.toml (leader)
[api]
bind = "0.0.0.0:8770"
token = "..."

Then one command on the leader prints everything a new machine needs:

node join
node join 100.100.20.100

Without an address it picks its best own one, first a Tailscale address, then one from the local network. The output is a join code of the form 10.0.0.1:8770#<key> and a ready one-liner each for Linux and Windows. On the new machine that one-liner is all it takes: the installer downloads MuteCloud, the setup recognises the code, checks the connection to the leader, suggests its own address, refuses node names that are already taken, offers the groups that are pinned to specific nodes and writes the configuration as a worker. If the installer is already running, pick "Add this machine to an existing network" in the setup and paste the code.

The code contains a join key, not the cloud's token. It can only add nodes, fetch the templates of their groups and enter the new node into game groups. On first connect the leader hands every node its own token, which the node uses from then on. node join new replaces the key without disconnecting anyone, node remove <name> locks a single node out and invalidates its token. The code still belongs only on machines you own, and node join requires full access.

Done by hand, the same looks like this on the worker:

mutecloud.toml (worker)
host = "10.0.0.2"

[cluster]
role = "worker"
leader = "10.0.0.1:8770"
token = "..."
advertise = "10.0.0.2"

advertise is the address under which the game servers of this node are reachable. The servers listen on it, and the proxy connects to that address.

Private network only

For security reasons, nodes cannot be joined over the open internet. A cluster needs an interface through which a node accepts commands, hands out templates including every plugin configuration and password, and starts game servers. Exposed to the internet, that interface would be an attack surface where a single intercepted or guessed token opens the entire network. On top of that, the proxy talks directly to the game servers on the other nodes, and those servers must not be reachable from the internet either.

MuteCloud enforces this on both sides. A worker only connects to a leader under a private address, and the setup already refuses other codes when they are entered. The leader only accepts nodes and template downloads from private networks and also refuses requests that arrive through a reverse proxy. Private means 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 100.64.0.0/10, and for IPv6 fc00::/7 and fe80::/10.

There are many ways to build that shared network:

OptionWhen it fits
Tailscalequickest to set up, works through NAT and home connections
HeadscaleTailscale with your own coordination server, no third-party accounts
NetBirdopen alternative to Tailscale, can be self-hosted
ZeroTiervirtual LAN, also without port forwarding
WireGuardconfigured by hand, lean and without extra services, needs one open UDP port
Your host's private networkservers at the same provider, for example a cloud network or VLAN
LANall machines are in the same local network

The leader's control port 8770 is only opened towards the private network, never towards the internet.

Where things live

The leader owns groups, ranks, roles, tokens, network texts and the shared storage. A worker forwards any request concerning them to the leader, so administration is possible from any node.

Templates are pulled by each worker. It compares its revision with the leader every thirty seconds, downloads only what has changed, unpacks the archive next to the existing copy and swaps the directories afterwards. If a transfer fails, the previous template remains in place.

Placement

Groups run on every node unless told otherwise:

nodes = ["node-1"]

The same at runtime, from any node and without a restart:

groups BedWars set nodes node-1,node-2   exactly these nodes
groups BedWars set nodes +node-3         add one node
groups BedWars set nodes -node-3         remove one node
groups BedWars set nodes all             run everywhere again

Placement of a new server follows the available memory. If no node has enough, the log reports it at most once every five minutes.

node          state of this node
node list     all nodes with memory and server count
node reload   re-read the configuration
node join new          new join key, older codes stop working
node remove <name>     lock a node out and invalidate its token
node restore <name>    lift the lock, the node needs a new code

When a node loses its connection, the webhook reports it and the players of that proxy are removed from the player list.

On this page