Quick answer: The most common cause is a missing GetLifetimeReplicatedProps implementation. Marking a UPROPERTY as Replicated in the header is not enough — you must also override GetLifetimeReplicatedProps and call DOREPLIFETIME for each replicated property.
Here is how to fix Unreal replicated variable not syncing. You marked a variable with UPROPERTY(Replicated), you change its value on the server, and the clients never see the update. The variable stays at its default value on remote machines. No errors, no warnings — just silent desync. This is the single most common Unreal multiplayer bug, and it almost always comes down to a missing registration step that the engine cannot warn you about at compile time.
The Symptom
You have a multiplayer game with a dedicated server or listen server setup. An actor has a UPROPERTY(Replicated) variable — say, a health value or a score counter. On the server, the value changes correctly. But when you check the same variable on a connected client, it remains at its initial value. The server and client are clearly out of sync.
If you are using ReplicatedUsing with a RepNotify callback, the callback function is never called on the client. You add print statements inside the callback and they never fire. The variable's value on the client stays frozen at whatever it was initialized to in the constructor or BeginPlay.
The project compiles without errors. The Unreal Header Tool does not flag any issues. The problem is entirely at runtime, making it especially hard to track down through normal debugging.
What Causes This
1. Missing GetLifetimeReplicatedProps implementation. This is the cause in the vast majority of cases. Adding Replicated to a UPROPERTY specifier tells UHT to generate metadata, but it does not automatically register the property with the replication system. You must override GetLifetimeReplicatedProps and explicitly call DOREPLIFETIME for every replicated property. If you forget this step, the net driver simply does not know the property exists.
2. Actor not set to replicate. The owning actor must have bReplicates = true set in its constructor. This is the master switch that tells the engine this actor participates in replication at all. Without it, none of the actor's properties will sync, regardless of how they are declared.
3. Value is being changed on the client instead of the server. Standard property replication is server-authoritative: the server pushes values to clients, not the other way around. If you change a replicated variable on a client, the change is local only and will be overwritten the next time the server sends an update. If the server never changes the value, clients never receive anything.
4. Replication condition filtering the update. If you use DOREPLIFETIME_CONDITION with conditions like COND_OwnerOnly or COND_InitialOnly, the property may not replicate to the client you expect. COND_OwnerOnly only sends to the client that owns the actor, and COND_InitialOnly only sends the value once when the actor is first replicated.
The Fix
Step 1: Implement GetLifetimeReplicatedProps with DOREPLIFETIME. This is the critical missing piece. Every replicated property must be registered in this function.
// MyCharacter.h
UCLASS()
class AMyCharacter : public ACharacter
{
GENERATED_BODY()
public:
AMyCharacter();
UPROPERTY(ReplicatedUsing = OnRep_Health)
float Health;
UPROPERTY(Replicated)
int32 Score;
UFUNCTION()
void OnRep_Health();
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
};
// MyCharacter.cpp
#include "Net/UnrealNetwork.h"
AMyCharacter::AMyCharacter()
{
// Enable replication for this actor
bReplicates = true;
Health = 100.f;
Score = 0;
}
void AMyCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
// Register each replicated property
DOREPLIFETIME(AMyCharacter, Health);
DOREPLIFETIME(AMyCharacter, Score);
}
void AMyCharacter::OnRep_Health()
{
UE_LOG(LogTemp, Log, TEXT("Health replicated to client: %f"), Health);
// Update health bar widget, play damage effects, etc.
}
Step 2: Ensure changes happen on the server. Always check authority before modifying replicated state.
void AMyCharacter::TakeDamage(float DamageAmount)
{
// Only the server should modify replicated variables
if (!HasAuthority())
{
UE_LOG(LogTemp, Warning, TEXT("TakeDamage called on client. Ignoring."));
return;
}
Health = FMath::Max(0.f, Health - DamageAmount);
UE_LOG(LogTemp, Log, TEXT("Server set health to %f"), Health);
// Replication happens automatically on the next net update
}
Step 3: Use the right replication condition. If you need to control which clients receive updates, choose the condition carefully.
void AMyCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
// Replicate to all clients (default behavior)
DOREPLIFETIME(AMyCharacter, Score);
// Replicate only to the owning client (e.g., ammo count)
DOREPLIFETIME_CONDITION(AMyCharacter, Health, COND_OwnerOnly);
}
Related Issues
If your replicated actor is working in the editor but crashes when you package the build, see our guide on packaged build crashes on startup. If overlap events are not firing on your replicated actors, check out overlap events not triggering, which covers collision setup issues that can compound with replication.
DOREPLIFETIME. You always forget DOREPLIFETIME.