How to Master Unreal Engine Dedicated Server Asset Stripping (Step by Step)
You boot up your newly compiled Unreal Engine dedicated server, expecting a lean, headless process. You pull a memory profile, and there it is: thousands of UMaterial, UTexture, and USoundWave objects sitting in your server's RAM.
The official documentation states that a headless server does not render visuals. So why is your server hoarding megabytes of texture data?
Every indie dev knows the moment their server hosting costs start threatening their runway. When a bare-metal machine can only host 10 instances of your game instead of 50 because of memory bloat, your backend architecture is compromised.
In this deep dive, we are going to dissect how unreal engine dedicated server asset stripping actually works under the hood, why ghost assets remain in memory, and how you can architect your C++ and Blueprints to completely eradicate them.
The Anatomy of a "Ghost Asset" on a Dedicated Server
To fix the problem, you must first understand what the Unreal Automation Tool (UAT) is doing when you cook for the Server target.
When a developer sees UTexture or UMaterial in a dedicated server memory profile, their first assumption is that the engine failed to strip the asset. This is only partially true.
Unreal Engine separates assets into two distinct parts:
- The UObject Wrapper: The metadata, properties, and reflection data.
- The Bulk Data: The actual heavy payloads (DXT compressed pixel data for textures, vertex buffers for meshes, PCM data for audio).
When you cook a dedicated server, the cooker successfully strips the bulk data. The rendering data is gone. However, the UObject wrapper remains.
If a Blueprint Class Default Object (CDO) has a hard reference to a UTexture2D, the server must instantiate the UTexture2D UObject to satisfy the reflection system and prevent null pointer crashes. Even though the bulk data is stripped and the texture might only consume 1KB instead of 10MB, the overhead of instantiating 50,000 of these UObjects adds up to significant memory bloat and garbage collection overhead.
Do Audio and Particles Follow the Same Logic?
Yes. If you hard-reference a USoundCue or a UNiagaraSystem, the server will load the UObject. The heavy PCM audio data is stripped, but the object exists.
With particle systems, this is actually dangerous. While visual rendering is skipped, if a particle system contains CPU-executed logic (like complex Niagara simulations or collision events), the server might actually tick that logic if not properly configured, burning precious CPU cycles alongside memory.
Step 1: Profiling the Server Memory Bloat
Before you start tearing apart your codebase, you need concrete numbers. You cannot optimize what you cannot measure.
Launch your packaged dedicated server with the following command-line arguments to enable memory tracking:
-LLM -LLMCSV -memoryprofiler
Once the server is running and a match has started, open the server console and execute:
memreport -full
This generates a .memreport file in your Saved/Profiling/MemReports directory. Open it in a text editor and search for Obj List. You will likely see an output resembling this:
Class UTexture2D: 1452 Objects, 1.25 MB
Class UMaterial: 840 Objects, 0.85 MB
Class USoundWave: 620 Objects, 0.45 MB
While 2.5MB doesn't sound catastrophic, this is just the raw object size. When you factor in the memory alignment, the name pool strings, the garbage collection tracking, and the cascading dependencies these objects pull in, a seemingly small visual reference chain can bloat a server from a baseline of ~150MB to over 600MB.
For more insights on aggressive server resource management and why every megabyte counts when scaling, check out our analysis on architecting zero-waste servers.
Step 2: Severing Hard References with Soft Pointers
The most common cause of ghost assets is hard references in C++ or Blueprints. If your ACharacter class has a hard pointer to a UI portrait, the server loads that UI portrait when it spawns the character.
The C++ Fix: TSoftObjectPtr
You must audit your headers and replace visual/audio hard references with soft references. Soft references only store the path to the asset, meaning the UObject is not loaded into memory until you explicitly call LoadSynchronous() or use the Streamable Manager.
BAD: Hard Reference (Loads on Server)
UCLASS()
class MYGAME_API AHeroCharacter : public ACharacter
{
GENERATED_BODY()
public:
// This forces the texture UObject to load on the dedicated server
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI")
UTexture2D* HeroPortrait;
// This forces the particle system to load
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "VFX")
UNiagaraSystem* UltimateAbilityVFX;
};
GOOD: Soft Reference (Server Stays Clean)
UCLASS()
class MYGAME_API AHeroCharacter : public ACharacter
{
GENERATED_BODY()
public:
// The server only holds a lightweight string path
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI")
TSoftObjectPtr<UTexture2D> HeroPortraitSoft;
// The server ignores the visual VFX entirely
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "VFX")
TSoftObjectPtr<UNiagaraSystem> UltimateAbilityVFXSoft;
void PlayUltimateVFX();
};
When the client needs to play the VFX, you check the net mode and load it asynchronously:
void AHeroCharacter::PlayUltimateVFX()
{
// Never execute visual logic on the dedicated server
if (GetNetMode() == NM_DedicatedServer)
{
return;
}
// Client-side execution
if (UltimateAbilityVFXSoft.IsPending())
{
// In a real scenario, use FStreamableManager for async loading
// to prevent hitches, but synchronous is shown here for simplicity
UNiagaraSystem* LoadedVFX = UltimateAbilityVFXSoft.LoadSynchronous();
if (LoadedVFX)
{
UNiagaraFunctionLibrary::SpawnSystemAttached(LoadedVFX, GetMesh(), ...);
}
}
}
The Blueprint Fix
In Blueprints, a hard reference is created anytime you assign an asset to a variable of type Texture2D (Object Reference), or if you use a node like Play Sound 2D and select a sound cue in the drop-down.
To fix this, change your Blueprint variables from Object Reference to Soft Object Reference. You will then need to use the Async Load Asset node before utilizing the asset on the client.
Step 3: Enforcing NeedsLoadForServer
Sometimes, you have entire components that are purely visual—like a custom UStaticMeshComponent used for character customization, or a UAudioComponent for footstep sounds.
You can tell the Unreal Engine package system to completely ignore these components when booting a dedicated server by overriding the NeedsLoadForServer function. This prevents the component (and everything it references) from even being instantiated.
// MyVisualCustomizationComponent.h
UCLASS(meta=(BlueprintSpawnableComponent))
class MYGAME_API UMyVisualCustomizationComponent : public UActorComponent
{
GENERATED_BODY()
public:
UMyVisualCustomizationComponent();
// Override the load behavior
virtual bool NeedsLoadForServer() const override;
};
// MyVisualCustomizationComponent.cpp
bool UMyVisualCustomizationComponent::NeedsLoadForServer() const
{
// Returning false guarantees this component is stripped from server memory
return false;
}
If you are writing a custom UObject that is strictly for client-side UI data, you can also use the UCLASS macro metadata to enforce this without overriding the function:
UCLASS(NeedsLoadForServer=false)
class MYGAME_API UClientOnlyUIData : public UDataAsset
{
GENERATED_BODY()
// ...
};
Step 4: Stripping Audio and Particles via Configuration
If you want a brute-force method to ensure certain directories are never cooked into your dedicated server, you can modify your DefaultGame.ini and DefaultEngine.ini.
Unreal Engine allows you to specify directories that should be excluded from specific targets. Since audio and particles often live in their own top-level folders, you can strip them out entirely during the cooking phase.
Open your DefaultEngine.ini and locate or add the [Core.System] and packaging sections:
[/Script/UnrealEd.ProjectPackagingSettings]
; Prevent the server from cooking client-only UI directories
+DirectoriesToNeverCook=(Path="UI/Widgets")
+DirectoriesToNeverCook=(Path="Cinematics/HighRes")
For audio specifically, you can disable the audio engine entirely on the server by ensuring your server target file (YourGameServer.Target.cs) has the following configuration:
public class YourGameServerTarget : TargetRules
{
public YourGameServerTarget(TargetInfo Target) : base(Target)
{
Type = TargetType.Server;
DefaultBuildSettings = BuildSettingsVersion.V2;
// Disables the audio engine compilation for the server
bCompileWithPluginSupport = true;
bDisableAudio = true;
}
}
Performance bottlenecks aren't just memory; CPU spikes from ticking unstripped particle components can lead to latency exploits. We've previously covered hard-armoring your Unreal Engine netcode against these exact vulnerabilities. Keeping your server tick rate stable requires ensuring no visual logic is running in the background.
Best Practices for Dedicated Server Memory Optimization
To ensure your server stays lean throughout the entire development cycle, implement these battle-tested rules:
- Separate Collision Meshes from Visual Meshes: Never use your high-poly visual mesh for server collision. Use a simplified, invisible
UStaticMeshfor the server's collision component, and make the visual mesh a client-only component. This prevents the server from loading complex physics data tied to the visual asset. - Audit Your Construction Scripts: Blueprint Construction Scripts run on both the client and the server. If your script spawns visual effects or loads materials, wrap that logic in a
Switch Has Authoritynode (checking for Authority vs Remote) or check the net mode, ensuring the server skips the visual instantiation. - Isolate Data Assets: Game designers love
UDataAssetfor balancing stats. However, if a weapon stat DataAsset also contains a hard reference to the weapon's firing sound, the server loads the sound when reading the damage value. Split your data intoUWeaponStatsData(server/client) andUWeaponVisualData(client-only). - Automate Memory Profiling in CI/CD: Do not wait until launch day to check your server memory. Add a step to your Continuous Integration pipeline that boots the server, runs
memreport, and fails the build if the baseline memory footprint increases by more than 10%. - Use the
ServerOnlyandClientOnlyModules: If you have a large project, split your C++ code into distinct modules. Place your UI, rendering, and audio C++ classes in aClientOnlymodule that is excluded from theServer.Target.csbuild. This physically prevents server code from referencing client assets.
Scaling Your Optimized Backend
Let's say you've followed these steps. You've audited your references, implemented soft pointers, and stripped your directories. Your dedicated server memory footprint drops from 800MB to a highly optimized 180MB.
This is a massive technical victory. You can now pack 4-5 times as many game instances onto a single bare-metal server, drastically reducing your AWS or Google Cloud hosting costs.
But building the infrastructure to actually deploy, scale, and orchestrate those lightweight servers is a completely different beast. Writing custom Kubernetes operators, setting up matchmaking queues, managing database sharding, and handling SSL certificate renewals takes easily 4-6 weeks of dedicated backend engineering work. That is time stolen directly from polishing your core gameplay loop.
With horizOn, these backend services come pre-configured. You simply upload your highly optimized Unreal Engine Linux server build, and horizOn handles the global fleet orchestration, auto-scaling up during player spikes and spinning down to zero when empty. You get to ship your game instead of spending months debugging your infrastructure.
Final Thoughts
Unreal Engine's dedicated server asset stripping is powerful, but it relies heavily on your architecture. The cooker cannot read your mind; if you tell it to hold a hard reference to a texture, it will obey, leaving a UObject ghost in your server's RAM.
By migrating to soft object pointers, leveraging NeedsLoadForServer, and strictly separating your visual data from your gameplay data, you can achieve the lean, high-performance headless server that multiplayer games demand.
Ready to scale your newly optimized multiplayer backend? Try horizOn for free and let us handle the orchestration, or check out our API docs to see how easily it integrates with Unreal Engine.