Rust Dedicated Server GC Allocations and Stutter: Tuning for 200-Player Wipes

Rust servers stutter for reasons that rarely show up in CPU graphs. A 200-player facepunch or modded wipe can sit at 25% total CPU and still produce a 400 ms hitch every few minutes, because the stall is not compute — it is garbage collection, and specifically allocation pressure from per-tick work that the server does not pool. Rust runs on Unity with Mono, and on a 200-player wipe the managed heap can reach 6–10 GB with hundreds of thousands of live entities, bases, and deployables. This is how to find the allocation pressure, reduce it, and tune the collector so hits become short rather than catastrophic.

Why Rust Stutters When CPU Looks Fine

Rust’s server ticks at a fixed rate but does far more per tick than most games: entity movement, building block stability, AI, player inventory and crafting, and the network serialisation of everything happening in the loaded grid. Each of those steps allocates temporary objects — strings for logging, LINQ enumerators, boxed structs, byte arrays for network buffers. When the allocation rate exceeds the young generation’s size, the collector runs; because the server is a long-lived process with a huge live set, a full collection must scan the entire heap and will stop the world while doing it.

The tell is a periodic hitch that correlates with player activity spikes, not with server restart or save. If a 200-player server on a 5 GHz core shows 30–45% CPU but players see 200–600 ms freezes every few minutes, and the freezes disappear when the server drops to 50 players, you are looking at GC, not at raw CPU. Confirm by enabling GC logging and checking whether the pauses align with gen2 collections.

Player countManaged heapTypical alloc ratePause symptomRAM floor
501.5–2.5 GB20–40 MB/sRare, <80 ms8 GB
1002.5–4 GB50–90 MB/sOccasional 100–200 ms12 GB
2004–8 GB120–250 MB/s300–800 ms every few min16–24 GB
300+ (modded)8–14 GB250–500 MB/s1–3 s freezes32 GB

Reading the GC Log

Start with evidence. Add -logFile /var/log/rust/gc.log -verbose to the server startup arguments if your host exposes them, or launch with +logFile for Unity’s player log. Look for lines containing GC_ or Mono’s GC: prefix and record pause durations. A healthy 200-player server should have gen2 pauses under 150 ms and gen0 pauses under 20 ms; gen2 pauses over 400 ms a few times an hour mean the live heap is too large or the allocation rate is promoting objects to the old generation early.

Without GC logs, infer from behaviour: run top -H -p $(pgrep -f RustDedicated) and watch for a single thread at 100% while all others block during a player-reported freeze. Mono’s collector is single-threaded in the Boehm configuration Unity ships, and that signature is unmistakable on pidstat.

Reducing Allocation Pressure

The fastest wins come from reducing per-tick work, not from collector flags. Server-side plugins are the primary suspect: plugins that run LINQ queries over all entities every tick, concatenate strings for logging on every hit, or create new collections per player per frame will dominate the allocation profile. Audit your plugin list and disable anything that runs on a per-tick loop; the well-written ones use the server’s OnServerInitialized and scheduled callbacks rather than OnTick.

Second, reduce logging. Rust’s console output is a managed string allocation per line, and verbose logging at 200 players can generate thousands of lines per second during combat. Set the server log level to warnings only for production wipes, and send the output to a file or journal rather than stdout captured by a panel that re-renders it. Community servers routinely recover 15–25% of allocation rate just by cutting debug logging.

Third, cap entity counts. Rust’s entity budget is the dominant factor in heap size; a wipe week where players build 120,000 deployables and 40,000 building blocks will produce a much larger live set than the same player count on a fresh map. Set entity limits per base and a global entity cap, and let the server clean up abandoned bases more aggressively (shorter decay). Smaller live heap means shorter gen2 scans, which is the single most effective lever you have.

Collector Flags That Help on Mono

Rust’s server exposes a limited set of Mono GC options through environment variables rather than full .NET Core configuration. The useful ones are MONO_GC_PARAMS=max-heap-size=… to cap the heap so the server does not balloon, nursery-size=… to size the young generation, and major=marksweep-par where the build supports parallel marking. A 64–128 MB nursery is a reasonable start for 200 players; beyond 256 MB usually increases pause length without reducing total GC time.

Do not blindly apply .NET Core flags like ServerGarbageCollection or GCLatencyMode — Mono ignores or partially ignores them, and copying them from a Minecraft or .NET guide wastes a wipe. Test one change per wipe and compare the pause percentiles; a change that lowers the average but raises the 99th percentile is a regression for players.

Hardware Choices That Reduce Stutter

GC pauses are stop-the-world, so the only hardware lever that shortens them is single-thread speed on the thread doing the collection and lower memory latency. A 5.0–5.7 GHz boost core with a large L3 cache (Ryzen 7 7800X3D or 7950X) can reduce gen2 pause times by 25–40% versus a 3.0–3.4 GHz server Xeon on the same heap, because the scan is memory-latency-bound and fast cores with big caches hide that latency. More cores do not help: the collector runs on one thread.

Memory speed matters too. DDR5-6000 CL30 on a Ryzen platform can cut heap scan time by 15–20% versus DDR5-4800 CL40, which is a real reduction in hitch length at 200 players. This is the opposite of the advice you would give for a database: for Rust, prioritise a fast core and fast memory over core count and capacity, then give the heap enough headroom that the OS never swaps. A 200-player server should have at least 2× the expected heap size in RAM so file cache and the OS stay resident.

Operational Habits That Keep Wipes Smooth

Habit one: restart the server every 24 hours at a low-population hour. Mono’s heap fragments over time, and a fresh process starts with a compact heap and full young generation. Servers that run seven days straight on a 200-player wipe often show a 30–50% larger heap by day six and noticeably longer pauses. A scheduled restart at 05:00 local, with save-then-restart and a 10-minute warning, costs almost nothing and resets the fragmentation clock.

Habit two: stagger your wipes. If you run multiple Rust servers on one host, do not wipe and restart all of them together — the post-wipe heap rebuild (new map, new entities, 200 players reconnecting) is the most allocation-heavy moment in the cycle. Stagger by 30–60 minutes so the collector never sees two servers rebuilding huge heaps on the same cores.

What Good Numbers Look Like

A well-tuned 200-player wipe on fast hardware should hold 30 tick with no user-visible stutter: gen0 pauses under 20 ms, gen2 pauses under 150 ms, heap steady-state between 4 and 7 GB, and total process RSS under 12 GB on a 24 GB machine. If your gen2 pauses are 400 ms or more, fix entity counts and logging before buying a bigger CPU — the cause is almost always allocation volume, not clock speed. When you do need a platform upgrade, pick one with the highest boost clock and the largest cache you can afford, as described in the high-clock dedicated server options for game hosting.

Finally, keep a baseline record from each wipe: player peak, entity count, heap size, and pause percentiles. After two or three wipes you will know exactly how your server behaves at 200 players and whether a given plugin or entity limit is helping or hurting. That record is more valuable than any single tuning flag, and it is what separates a server that survives a viral wipe from one that melts under the dedicated hosting capacity you bought for last season’s population.

Leave a Reply