Running several game servers on one machine is the fastest way to cut cost-per-slot, but only if you budget CPU threads, RAM, and disk I/O correctly. Pack too aggressively and a single Rust wipe or Minecraft chunk-gen storm will starve every other instance on the box. This guide gives you the numbers and the Linux configuration to stack instances safely.
The Three Resources That Actually Contend
- CPU single-thread performance — most game server binaries are still largely single-threaded for their main tick loop. A 32-core EPYC does not help a single Minecraft instance beyond 2–4 threads.
- RAM bandwidth and capacity — each instance holds its world, entity lists, and JVM/allocator overhead resident. RAM is the resource that decides how many instances fit.
- Disk I/O and IOPS — world saves, autosaves, log flushing, and map generation compete for NVMe queue depth. This is the one most people forget until autosave stutter appears.
Practical Per-Instance Resource Budgets
| Game | RAM per instance | Tick threads | Peak disk I/O |
|---|---|---|---|
| Minecraft Vanilla (40 players) | 6–8 GB heap | 2 | 40–80 MB/s during chunk gen |
| Minecraft Modded (20 players) | 10–14 GB heap | 2–3 | 80–150 MB/s during world gen |
| Rust (100 players) | 8–12 GB | 2 | 60–120 MB/s on wipe day |
| Valheim (10 players) | 3–4 GB | 1–2 | 20–40 MB/s |
| CS2 / FPS (16 slots, 128 tick) | 1–2 GB | 1 | <10 MB/s |
A 64 GB machine realistically runs four mid-size Minecraft instances or two heavy modded instances plus a handful of lightweight FPS servers — not eight of anything. Reserve 4–8 GB for the OS, page cache, and your monitoring stack.
CPU Pinning Without Starving Anything
Modern CPUs have SMT siblings that share execution units. Pinning two game servers to the two hyperthreads of one physical core gives you worse jitter than leaving them unpinned. Pin to physical cores, or pairs of adjacent cores on the same CCD, and skip hyperthread siblings where possible.
With systemd, bind each instance to a core set and give it a slice so one server cannot eat the whole machine:
systemctl set-property [email protected] CPUAffinity=4-7 CPUQuota=250%
systemctl set-property [email protected] CPUAffinity=8-11 CPUQuota=200%
CPUQuota=250% lets the JVM use 2.5 cores at most. Combined with affinity, this keeps GC pauses from spilling into your neighbour instance’s tick budget. On high-core-count dedicated hardware you have enough physical cores to give each game its own CCD and its own NUMA node, which also avoids cross-socket memory latency penalties.
Disk I/O Isolation
All instances sharing one NVMe is fine until wipe day. Then a single Rust server’s world generation saturates the device and every other server’s autosave blocks behind it. Two mitigations that work:
- Separate NVMe devices for world data, and stagger autosave intervals so they never overlap (e.g. instance A at :00, B at :05).
- Use cgroup v2 io.max to cap each instance’s read/write bandwidth so no single server can monopolise the device.
# cap instance to 400MB/s read, 300MB/s write on 259:0
echo "259:0 rbps=419430400 wbps=314572800" > /sys/fs/cgroup/games.slice/minecraft-survival.slice/io.max
Network: Port Layout and Bandwidth
Give each game a contiguous port block and its own systemd unit so restarts are independent. For bandwidth planning, budget roughly 30–60 Kbps per player for survival games and 15–25 Kbps for shooters at high tick rates, then add 20% headroom. Ten simultaneous instances at 100 players each will push several hundred Mbps — well within a 1 Gbps uplink but worth measuring rather than guessing. If you are unsure where your traffic ceiling sits, the bandwidth planning guidance on bestdedicatedwebhostingserver.com walks through commit vs burst billing so you don’t overpay for peak capacity you use twice a month.
Monitoring Multi-Instance Health
Track four metrics per instance and alert on them, not on raw CPU:
- Tick duration (P95 and P99) — the only metric players feel.
- Resident memory vs heap limit — catches leaks before OOM kills.
- Disk await time on the world device.
- Steal time — if this is non-zero on dedicated hardware, your hypervisor or cgroup limits are misconfigured.
Export all four to Prometheus and set tick-duration P99 alerts at 1.5x your target frame time. A server running at 60 tick should alert above 25 ms, not above 100 ms.
When One Box Stops Being Enough
The switch point is not player count — it is when your busiest instance’s P99 tick time degrades because of a neighbour. If RAM is exhausting first, add capacity. If single-thread performance is the wall, no amount of RAM helps and you need hardware with higher clocks rather than more cores. Specifying that trade-off correctly before you order is where most operators lose money; comparing dedicated plans that prioritise clock speed over core count is usually cheaper than over-provisioning cores you cannot use.



Leave a Reply
You must be logged in to post a comment.