Receiving Exec Commands on a Dedicated Server Through the Stats Port

Article written by Alex K.

A major advantage of a dedicated server is that it runs headless, without any game window. However, it can be helpful, especially when testing, to be able to run commands on a dedicated server. While you can use server RPCs from a connected client to invoke behavior on the server, there may be cases where having a connected client is not possible or wanted. Fortunately, you can use the server’s stats port to receive exec commands over the network, and this article will cover how to set this functionality up.

First, you’ll want to make sure the WITH_PERFCOUNTERS macro is set, and you can specify the port number of the stats port on the command line using “-statsPort=”.

Next, you’ll need to bind a delegate to FPerfCounters::OnPerfCounterExecCommand to handle game-specific commands. This can be done in the GameInstance, for example:

IPerfCountersModule& PerfCountersModule = FModuleManager::LoadModuleChecked<IPerfCountersModule>("PerfCounters");
 
IPerfCounters* PerfCounters = PerfCountersModule.CreatePerformanceCounters();
if (PerfCounters != nullptr)
{
    PerfCounters->OnPerfCounterExecCommand() = FPerfCounterExecCommandCallback::CreateUObject(this, &ThisClass::PerfExecCmd);
}

For an example of the delegate:

bool UYourGameInstance::PerfExecCmd(const FString& ExecCmd, FOutputDevice& Ar)
{
	FWorldContext* MyWorldContext = GetWorldContext();
	if (MyWorldContext)
	{
		UWorld* World = MyWorldContext->World();
		if (World)
		{
			if (GEngine->Exec(World, *ExecCmd, Ar))
			{
				return true;
			}
			Ar.Log(FString::Printf(TEXT("ExecCmd %s not found"), *ExecCmd));
			return false;
		}
	}
	Ar.Log(FString::Printf(TEXT("WorldContext for ExecCmd %s not found"), *ExecCmd));
	return false;
}

After attaching the exec command handler, you may need to call FHttpServerModule::Get().StartAllListeners(); to begin listening on the stats port. You should see a line in the server’s log saying something along the lines of “Created new HTTPListener on :”.

Once the server is listening on this port and there is an exec command delegate bound to the perf counter, you can send commands to the server using a tool such as curl with the following format: http://<address>:<statsport>/exec?c=<command>;
For example: curl http://127.0.0.1:24002/exec?c=debug%20crash

Finally, while enabling this can be useful for allowing communication with your dedicated server instances, it’s important to also consider the security implications of this feature. You’ll want to make sure not just anyone can send commands to be executed on your server, and there are a number of methods you can use to ensure your servers’ security, such as simply disabling the feature in shipping builds or using a firewall to filter incoming commands.