Standalone Match-maker
In the default Colyseus setup, matchmaking and room handling run together in the same process. You can separate them so that a dedicated match-maker process handles all matchmaking logic, while separate game server processes handle the actual room connections.
βββββββββββββββββββββββββββ
β Match-maker Process β β handles join/create/query
β (no rooms here) β
ββββββββββββββ¬βββββββββββββ
β IPC via Redis
ββββββββ΄βββββββ
βΌ βΌ
βββββββββββββ βββββββββββββ
β Game Srv β β Game Srv β β rooms run here
β (rooms) β β (rooms) β
βββββββββββββ βββββββββββββThis architecture is useful when you want to:
- Have a single entry point for matchmaking while distributing room workload across multiple game servers
- Scale game servers independently from the matchmaking layer
- Run the match-maker behind a lightweight HTTP service without a WebSocket transport
Requirements
Both the match-maker process and the game server processes must share the same Presence (e.g. Redis) and Driver (e.g. Redis or Postgres) so they can communicate via IPC and share room cache state.
Game Server Processes
Each game server process runs a full Colyseus server with its rooms defined using defineServer(). These processes handle the actual WebSocket connections and room logic.
import { defineServer, defineRoom } from "colyseus";
import { RedisPresence } from "@colyseus/redis-presence";
import { RedisDriver } from "@colyseus/redis-driver";
import { WebSocketTransport } from "@colyseus/ws-transport";
import { BattleRoom } from "./rooms/BattleRoom";
const gameServer = defineServer({
transport: new WebSocketTransport(),
presence: new RedisPresence(),
driver: new RedisDriver(),
publicAddress: "game-server-1.example.com:2567",
rooms: {
battle: defineRoom(BattleRoom),
},
});Each game server must have a unique publicAddress so that the client SDK knows where to establish its WebSocket connection after receiving a seat reservation.
Match-maker Process
To run a standalone match-maker, use defineServer() with the isStandaloneMatchMaker option set to true. This tells Colyseus that this process will only handle matchmaking β it will not spawn rooms locally, subscribe to IPC room creation requests, or register itself as an available game server process.
import { defineServer, defineRoom } from "colyseus";
import { RedisPresence } from "@colyseus/redis-presence";
import { RedisDriver } from "@colyseus/redis-driver";
import { BattleRoom } from "./rooms/BattleRoom";
const server = defineServer({
isStandaloneMatchMaker: true,
presence: new RedisPresence(),
driver: new RedisDriver(),
rooms: {
battle: defineRoom(BattleRoom),
},
});When isStandaloneMatchMaker is enabled, the process:
- Skips IPC subscription β it will not receive or handle remote room creation requests
- Skips health checks β it does not register itself in the process stats, so game servers will never try to create rooms on it
- Delegates room creation β when a matchmaking request requires a new room, the default
selectProcessIdToCreateRoomwill select one of the available game server processes
The match-maker process must register the same room types as the game servers. Room classes are needed for filterBy() options and onAuth() validation during matchmaking, even though rooms are not instantiated on this process.
Custom process selection
By default, rooms are created on the game server process with the fewest rooms. You can override this with selectProcessIdToCreateRoom:
import { defineServer, defineRoom } from "colyseus";
import { RedisPresence } from "@colyseus/redis-presence";
import { RedisDriver } from "@colyseus/redis-driver";
import { BattleRoom } from "./rooms/BattleRoom";
const server = defineServer({
isStandaloneMatchMaker: true,
presence: new RedisPresence(),
driver: new RedisDriver(),
selectProcessIdToCreateRoom: async (roomName, clientOptions) => {
// Custom logic to select which game server should create the room
},
rooms: {
battle: defineRoom(BattleRoom),
},
});How it works
- A client sends a matchmaking request (e.g.
joinOrCreate) to the match-maker process - The match-maker finds or creates a room β room creation is delegated to a game server process via IPC
- The match-maker returns a seat reservation containing the
sessionId,roomId, and the game serverβspublicAddress - The client SDK uses the
publicAddressto establish a WebSocket connection directly to the game server hosting the room
Clients receive a seat reservation with the publicAddress of the game server that created the room. The client SDK uses this address to connect directly to the game server β traffic does not flow through the match-maker process after matchmaking.