Dedicated Server Performance
Server frame budget at 30 Hz is 33.3 ms; at 60 Hz it's 16.6 ms shared across game tick, AI, physics, and replication. The headline is replication, but the interesting wins live in the gap between "default NetUpdateFrequency=100" and "Fortnite ships 100 players with ~50,000 replicated actors per match." This tutorial covers the server build target, the dormancy/push-model/Iris ladder, AI throttling, and the per-class dormancy bugs that quietly decimate server CPU.
Why server perf is its own discipline
A dedicated server has no GPU, no audio, no UI — and pays for none of those budgets. What it does pay for is replication. On a server, the entire frame budget is devoted to game tick + AI + physics + replication, and replication can take more than half of it. Frame loss on a server cascades to every connected client as input lag and prediction-correction snaps.
Reference scale: Fortnite Battle Royale runs 100 connected players with roughly 50,000 replicated actors per match (per Replication Graph docs). That's the canonical "this is what made Replication Graph necessary" stat.
The Server target & no-graphics path
A proper dedicated server uses a server-typed target, not just runtime flags:
using UnrealBuildTool;
using System.Collections.Generic;
public class YourGameServerTarget : TargetRules
{
public YourGameServerTarget(TargetInfo Target) : base(Target)
{
Type = TargetType.Server;
DefaultBuildSettings = BuildSettingsVersion.V5;
IncludeOrderVersion = EngineIncludeOrderVersion.Latest;
ExtraModuleNames.AddRange(new string[] { "YourGame" });
}
}
YourGameServer.exe MyMap -log -nullrhi -unattended -nosound -NoVerifyGC
Type=TargetType.Server is what strips the editor-only modules and render-thread setup at compile time. The runtime -nullrhi flag alone is not enough — without the typed target you ship a render thread that does nothing but eat memory.
?listen on a server command line silently makes it a listen server with a hidden local player. Two replication paths now run; CPU near-doubles. The dedicated-server flag is plain -server; ?listen belongs only on listen-server / single-host configurations.
Server-only cooks
A server-only cook strips content the server doesn't need: textures over 64×64, lightmaps, audio, particle render data. The build pipeline:
RunUAT BuildCookRun -project=YourGame.uproject ^
-server -serverplatform=Linux -noclient ^
-cook -stage -archive ^
-archivedirectory=Builds/Linux_Server
-noclient + -server tell UAT to skip the client-side cook entirely. Per-asset, you can mark cook rules as NotForServer in primary asset types or per-class. The server cook is typically 50–70% smaller than the client cook on shipped multiplayer titles.
Tick rate strategy — 30 vs 60 Hz
NetServerMaxTickRate defaults to 30 Hz. Configured in DefaultEngine.ini under [/Script/Engine.NetDriver] — not a runtime CVar.
[/Script/Engine.NetDriver] NetServerMaxTickRate=30 ; 60 Hz roughly doubles per-frame replication CPU. ; Worth it on competitive shooters; not for BR/MMO.
Picking 30 vs 60 by genre is the right starting point: BR/MMO at 30, FPS/fighter at 60. Doubling tick rate does not halve latency — it halves the variance of the worst-case packet arrival, which matters for projectile prediction and hit reg, but the mean trip is dominated by network distance.
The replication budget & mitigation ladder
"The frame is for replication" is the right mental model on a busy server. Mitigations in priority order:
- Dormancy —
DORM_DormantAllfor static interactables,DORM_Initialfor spawn-once objects. Per Epic, "potentially saves multiple milliseconds of server CPU time per frame." - Push model —
net.IsPushModelEnabled=1,bIsPushBased=trueon UPROPERTY, plusbWithPushModel=truein theTarget.cs. Skips dirty-check polling on properties marked push-model. Community-measured: 4-client test droppedNetBroadcastTickTimefrom 167.4 ms to 76.5 ms (>2× speedup). - Adaptive net update frequency —
net.UseAdaptiveNetUpdateFrequency=1; engine throttles per-actor betweenNetUpdateFrequencyandMinNetUpdateFrequency. - NetCullDistanceSquared — default 225,000,000 cm² ~ 15,000 cm (150 m) relevancy radius. Lower for actors that don't matter at distance.
- Replication Graph — spatial-hash relevancy; what Fortnite ships.
- Iris — the new replication system, experimental in 5.5, more mature in 5.6+.
NetBroadcastTickTime 66.2 ms / server 10–15 fps; Iris NetBroadcastTickTime 45.8 ms / server 15–20 fps. Patched-Iris dropped broadcast to 29.5 ms (~55% below default). Source: Iris: 100 Players in One Place.
Dormancy in practice
Dormancy is the single highest-leverage server optimization. The states:
DORM_Awake— default; actor replicates every server tick.DORM_DormantAll— replicates only whenFlushNetDormancy()is called or property dirty.DORM_Initial— replicates exactly once at relevancy and never again unless flushed. Use for spawn-once props.
Common dormancy bugs documented in the community:
- Blueprint-replicated UPROPERTYs ignore dormancy. Actor stays
DORM_Initial, BP variables still send (per Vorixo's DORM_Initial deep dive). ForceNetUpdatesilently no-ops whenNetDriver==nullptr, e.g., beforeUWorld::Listen(). UseFlushNetDormancydirectly.- Mutating a replicated property without a manual flush on a dormant actor — the property change replicates but only because of the implicit dirty-flag; some BP-mutated paths don't trigger it.
Replication Graph vs Iris
Replication Graph is mature, Fortnite-proven, the right call for any title with serious player counts on UE 5.0–5.4. Spatial 2D grid (UReplicationGraphNode_GridSpatialization2D), AlwaysRelevantNode, ConnectionGraphNode, FastSharedReplicationPath. Enable by setting ReplicationDriverClassName in DefaultEngine.ini to your custom UReplicationGraph-derived class.
Iris is the new system, default-compiled since 5.1, experimental in 5.5, target for 5.6+. Replaces the legacy NetDriver replication path with NetTokens, ReplicationFragments, and a fundamentally different bandwidth model. Enable per-world via net.Iris.UseIrisReplication=1 and opt new actors into the SubObject path with net.SubObjects.DefaultUseSubObjectReplicationList=1.
Don't migrate to Iris yet for a shipping title. Verify on a soak test first; Iris buffer growth is monotonic per Epic's documented gotcha — once peak replicated-object count blows the buffer, it stays expanded for the lifetime of the server process.
AI throttling on the server
The server runs every NPC's AI; clients run almost none. AI is therefore proportionally a much bigger server-CPU concern than it is on a single-player shipped title.
The mitigation stack:
- Significance Manager — Epic's plugin for tick-rate decimation by significance (typically distance + screen-space). Tom Looman's optimization talk frames this as the "tick budget" pattern.
- Mass Entity — for >500 AI, the data-oriented Mass framework instead of one Actor per entity. Far cheaper per agent.
- BehaviorTree decorator throttling — gate expensive selectors on cheap predicates that don't run every tick.
- AI Perception sense interval — default 0.2 s. Don't drop without measuring; raising to 0.5 s on AI that don't need fast reactions is a measurable win.
Networked physics on the server
Physics on the server is a frequent source of regression. The pitfalls:
- Variable game tick + fixed physics tick mismatch is the #1 desync source. If you need rewind/resim networking, switch to Async Physics Tick + Fixed-Tick mode (covered in the Chaos Physics tutorial).
- Replicating physics state for every body is expensive in bandwidth. Replicate the spawn event, simulate locally; only replicate authoritative transforms for hero objects.
- Substepping is mutually exclusive with Async Physics Tick. Pick one based on your network model.
Server memory & profiling
Typical 4–8 GB headless target on cloud-hosted dedicated servers. The cost is dominated by world content + per-connection state (replication subobject lists, Iris buffers, dormancy bookkeeping).
Profiling the server:
stat net—NetTickTime,NetBroadcastTickTime, replicated property counts. Primary triage view.stat game— gameplay tick + replication time slices.net.Debug.ActorClassNameTypeCSV 1(5.6+, dedicated servers, Non-Shipping) — per-class CSV stats forForceNetUpdate/FlushNetDormancycalls/sec. The new "where is my dormancy regression" stat.- Insights with
-tracehostfrom the dedicated server — full timing in real-time. obj list class=Actor outer=World— live actor count on the server (sanity check vs replicated count).
The 30 vs 60 Hz tradeoff matrix
The right tick rate depends on game design and content scale, not just preference:
| Genre | Recommended Hz | Replication strategy | Reference |
|---|---|---|---|
| Battle Royale (100 players) | 30 | Replication Graph + dormancy + adaptive freq | Fortnite |
| Competitive FPS (10–32 players) | 60 | Iris (5.6+) or RepGraph + push model | Lyra reference |
| Co-op shooter (4–16 players) | 60 | Default NetDriver + push model + dormancy | Many shipped titles |
| MMO (100+ players, large world) | 20–30 | Replication Graph + heavy dormancy + Iris when stable | WP-streamed servers |
| Fighting / 1v1 | 60 | Default + fixed-tick networked physics | Lyra-class |
PerfGuard baselines per scenario can capture an Insights trace from the headless server under a scripted load (N AI bots, M replicated actors), and gate two metrics in CI: server frame time at p99, and NetBroadcastTickTime as a percentage of frame budget. Bonus: scrape the 5.6 net.Debug.ActorClassNameTypeCSV output to flag any class whose ForceNetUpdate / FlushNetDormancy calls/sec doubled vs baseline — the dormancy-regression early-warning signal.
- Networking & Replication Performance — the deeper version of section 5 (Replication Graph internals, Fast Array Serializer).
- World Partition & Streaming — the dedicated-server-streaming gotcha covered there matters here too.
- Chaos Physics Performance — networked physics integration.