Quick answer: On a listen server the host IS a client, so HasAuthority() returns true on the host’s actor copies. Replicated variables need GetLifetimeReplicatedProps with DOREPLIFETIME, and OnRep functions never fire on the server because the server sets the value directly.

Unreal Engine’s replication system is powerful and well-documented, but the listen server model introduces a layer of confusion that catches even experienced developers off guard. In a dedicated server setup, the authority and the clients are cleanly separated. In a listen server, the host machine wears both hats simultaneously — it is the server and a client at the same time. This dual role creates a category of bugs where code works correctly in a dedicated server test but silently misbehaves when players host their own games. This guide unpacks the most common failures and how to diagnose them.

Dedicated Server vs Listen Server vs Standalone: What Actually Differs

Before debugging, it helps to be precise about what each mode means:

The critical implication: code that guards on HasAuthority() to run “server-only” logic will run on the listen server host’s client session as well. If that logic has side effects that should only happen once (spawning an actor, deducting currency, saving state), the listen server host will trigger it while remote clients will correctly route through an RPC.

Server RPCs Called from the Listen Server Host Execute Locally

This is the most common source of listen-server-specific bugs. A Server RPC (marked Server in Blueprint or UFUNCTION(Server, Reliable) in C++) is meant to be called on a client and executed on the server. When the listen server host calls a Server RPC, the engine detects that the caller already has authority and executes the function inline — it never goes through the network stack.

// C++ Server RPC declaration
UFUNCTION(Server, Reliable, WithValidation)
void ServerPickupItem(AItemActor* Item);

// Implementation (runs on server for remote clients,
// runs locally on the listen server host)
void AMyCharacter::ServerPickupItem_Implementation(AItemActor* Item)
{
  // This runs immediately on the host, not via RPC
  Item->Destroy();
  Inventory->AddItem(Item->GetItemData());
}

For most game logic this is fine — the result is the same whether it went through the network or not. The problem arises when the RPC implementation has assumptions about latency (e.g., checking a “still pending” flag that a remote client’s prediction system set) or when you test exclusively with a dedicated server and never catch that the local execution path exists.

DOREPLIFETIME: Replicated Variables That Never Replicate

Variables marked UPROPERTY(Replicated) in C++ must also be registered in GetLifetimeReplicatedProps. Forgetting this registration is a silent failure — the compiler does not warn you, and the variable simply never replicates.

// In your actor header
UPROPERTY(Replicated)
int32 Health;

// In your actor .cpp — this function MUST be implemented
void AMyActor::GetLifetimeReplicatedProps(
  TArray<FLifetimeProperty>& OutLifetimeProps
) const
{
  Super::GetLifetimeReplicatedProps(OutLifetimeProps);
  DOREPLIFETIME(AMyActor, Health);
  // Conditional replication (only to owning client):
  DOREPLIFETIME_CONDITION(AMyActor, Health, COND_OwnerOnly);
}

In Blueprint, replicated variables are configured via the variable’s Replication dropdown in the My Blueprint panel. The options are None, Replicated, and RepNotify. Selecting Replicated is the Blueprint equivalent of DOREPLIFETIME.

OnRep Functions Do Not Fire on the Server

ReplicatedUsing variables (Blueprint: RepNotify) trigger their OnRep_ function only on machines that receive the replicated value from the network. The server sets the value directly — it does not receive it via replication — so OnRep_ never fires on the server.

On a listen server, this means the host’s client viewport also never fires OnRep_ for variables the host’s server copy sets. Remote clients will fire it correctly. The fix is to explicitly call your notification function after setting the value on the server:

void AMyActor::SetHealth(int32 NewHealth)
{
  Health = NewHealth;
  // OnRep won’t fire here — call it manually on server/host
  OnRep_Health();
}

This is the canonical pattern in the Unreal community and is safe to call twice — once here, and once when replication pushes the value to remote clients. Design your OnRep_ function to be idempotent.

NetUpdateFrequency and Relevancy Causing Missed Updates

NetUpdateFrequency controls how many times per second the server sends replication updates for an actor. The default for AActor is 100 Hz, but many subclasses set it lower. If your actor’s NetUpdateFrequency is 1 Hz and a variable changes twice in one second, only the final value may be replicated — the intermediate state is never seen by clients.

Relevancy is the other factor. Actors outside a client’s Net Relevancy range do not send replication data at all. When the actor comes back into range, it receives a full state sync — but any rapid changes that occurred while out of range are lost. For gameplay-critical state, use bAlwaysRelevant = true or implement IsNetRelevantFor to keep key actors always relevant.

Using Network Emulation in PIE to Test Listen Server Behavior

The fastest way to reproduce listen-server-specific bugs without a second machine is Unreal’s built-in Network Emulation. In the editor, set Play → Number of Players to 2 and Net Mode to "Play As Listen Server". This opens two game windows: the top-left is the listen server host, the others are simulated remote clients.

// Console commands useful during PIE network testing
net.PacketLag 100        // add 100ms simulated latency
net.PacketLoss 5         // drop 5% of packets
p.NetShowCorrections 1   // visualize prediction corrections
log LogNet Verbose        // verbose replication log

Run your problem scenario with the client window active, not the host window. If the bug only appears in the client window, you have confirmed it is a replication issue rather than a logic bug. If it appears in both windows, the bug is likely in the server-side code itself.

“A listen server bug is usually a dedicated server assumption that snuck into your code — the moment you remember the host is also a client, most of them become obvious.”

When in doubt, run three PIE windows: one dedicated server, two clients — if the bug disappears compared to listen server mode, you’ve found your authority assumption.