Build a headless Unity 6 dedicated server by hand, start to finish
Unity 6 dedicated server builds by hand: Hub modules, UNITY_SERVER, BuildPlayerOptions, batch-mode launch, idle CPU fix, and one line for CI.
A multiplayer game needs a server executable that runs on a machine with no monitor, no GPU and nobody at the keyboard. Unity 6 has a build target for that. This is the whole manual route, from Hub module to upload.
What the Dedicated Server target is
Dedicated Server is not a separate platform. It is a subtarget of the three desktop platforms (Windows, Linux and macOS), designed to reduce the CPU, memory and disk requirements of a server build. The result is a headless executable with no graphical interface. Most of the saving comes from code and asset stripping.
By default a Dedicated Server build does the following, according to the Unity 6.3 manual:
- Deactivates the audio subsystem.
- Removes the lighting threads.
- Disables player loop callbacks a server does not need, such as
SendMouseEvents,UpdateAllRenderers,PlayerUpdateCanvases,UpdateAudio,UIElementsUpdatePanelsandUpdateInputManager. - Strips textures and meshes that have CPU Read/Write disabled, because no graphics device is ever initialized. Assets with Read/Write enabled, assets in Resources folders, texture metadata and mesh data that physics needs are kept.
The Player Settings option Enable Dedicated Server optimizations goes further and removes shaders and fonts. Leave it off until you have confirmed nothing references them at runtime. None of these optimizations apply to content built with the Scriptable Build Pipeline, so Addressables and Entities subscenes do not shrink.
Only the Other Settings section of Player Settings applies to the server. Icon, Resolution and Presentation, Splash Image and Publishing Settings are ignored.
Install the module from Unity Hub
The server subtarget ships as a separate Hub module per platform. In Unity Hub open Installs, pick the editor version, select Manage, then Add modules. The Linux one is called Linux Dedicated Server Build Support; the Windows one follows the same naming. In the publisher's setup, Linux server builds also needed Linux Build Support (Mono). Without the module, the Platform Browser shows the server platform with an Install with Unity Hub button instead of letting you select it.
Build from the editor
Open File > Build Profiles, select Add Build Profile, and choose Linux Server or Windows Server in the Platform Browser. Select Switch Profile to make it active, then Build.
Server-only code with UNITY_SERVER
Unity defines the UNITY_SERVER scripting symbol for every Dedicated Server build. Use it for code that should only exist on the server.
#if UNITY_SERVER
// Server-only code: open the listen port, start the match loop.
#else
// Client-only code: connect, render, read input.
#endif
Unity sets it for you. A second symbol you control yourself goes in per build, as shown next, rather than into Player Settings.
Build from code with BuildPlayerOptions
Switching the active profile by hand every release is where mistakes creep in; a small editor script makes it repeatable. One detail from the manual: subtarget is an int, so cast the enum (StandaloneBuildSubtarget.Server is 1).
using UnityEditor;
using UnityEditor.Build.Reporting;
public static class ServerBuild
{
[MenuItem("Build/Linux Server")]
public static void BuildLinux() => Build(BuildTarget.StandaloneLinux64, "Builds/linux-server/MyGame.x86_64");
[MenuItem("Build/Windows Server")]
public static void BuildWindows() => Build(BuildTarget.StandaloneWindows64, "Builds/windows-server/MyGame.exe");
static bool Build(BuildTarget target, string path)
{
var options = new BuildPlayerOptions
{
scenes = new[] { "Assets/Scenes/Server.unity" },
locationPathName = path,
target = target,
subtarget = (int)StandaloneBuildSubtarget.Server,
extraScriptingDefines = new[] { "SERVER_BUILD" },
options = BuildOptions.None,
};
BuildReport report = BuildPipeline.BuildPlayer(options);
return report.summary.result == BuildResult.Succeeded;
}
}
extraScriptingDefines are appended to the Scripting Define Symbols from Player Settings for this build only. Player Settings stay untouched, so the editor keeps compiling exactly as before and nothing leaks into your client build. If you leave both target and subtarget unset, Unity uses the target and subtarget of the current Build Profile instead.
One line for CI
For a build server, call the same method with -executeMethod. The method must be static and the script must live in an Editor folder.
Unity -batchmode -nographics -quit -projectPath /srv/mygame \
-executeMethod ServerBuild.BuildLinuxCli -logFile build.log
Exit codes come from two rules in the manual: an unhandled exception makes Unity exit with return code 1, and EditorApplication.Exit(n) exits with the code you choose. So the CLI entry point is three lines:
public static void BuildLinuxCli() =>
EditorApplication.Exit(Build(BuildTarget.StandaloneLinux64,
"Builds/linux-server/MyGame.x86_64") ? 0 : 1);
If you prefer to select the platform on the command line, -buildTarget Linux64 -standaloneBuildSubtarget Server sets the active target and subtarget before the project loads. On Windows, start Unity with Start-Process -Wait (PowerShell) or start /wait (cmd) so your pipeline reads the exit code.
Run it
-batchmode runs the application in headless mode: it displays nothing and accepts no user input. -nographics keeps Unity from initializing a graphics device, which is what lets the server run on a machine with no GPU. The manual notes that output logs are turned off in that mode, so add -logFile - to send the log to the console, where systemd, Docker or your process manager can collect it.
#!/bin/sh
cd "$(dirname "$0")"
exec ./MyGame.x86_64 -batchmode -nographics -logFile - "$@"
The Windows version is the same idea:
@echo off
cd /d "%~dp0"
MyGame.exe -batchmode -nographics -logFile - %*
Extra arguments pass through, so ./run.sh -port 7777 reaches your code.
Why an idle server burns a whole core
Application.targetFrameRate defaults to -1. On desktop, with QualitySettings.vSyncCount at 0, that means "render unsynchronized as fast as possible". A headless server has no display and no vertical sync, so its player loop spins through frames with nothing to wait for. In the publisher's test, an idle Windows server used a full CPU core without a cap and near zero with one.
The fix is one line, run early in a server-only path:
#if UNITY_SERVER
QualitySettings.vSyncCount = 0;
Application.targetFrameRate = 30;
#endif
Pick the rate your simulation needs. Unity 6.3 also parses a -framerate command-line flag into DedicatedServer.Arguments.TargetFramerate and applies it to Application.targetFrameRate. When the flag is absent and nothing is configured, that property is null and no cap is applied, so a default in code is still worth having.
What not to upload
A build folder holds more than the server. On Windows, *.pdb symbol files appear when Copy PDB files is enabled in the Build Profiles window. On Linux, Development Build adds *.pdb files for managed code and *_s.debug files for native code. With the IL2CPP backend, the build also contains GameAssembly.so (needed) and a folder named MyGame_BackUpThisFolder_ButDontShipItWithYourGame (not needed) that holds intermediate files for debugging. Keep the folder for crash analysis; keep it out of the zip.
robocopy Builds\linux-server Builds\ship /E /XD *BackUpThisFolder* /XF *.pdb *_s.debug
Compress-Archive -Path Builds\ship\* -DestinationPath Builds\linux-server.zip -Force
Build into an empty folder each time, or last week's development DLL ships with this week's server.
Where Dedicated Server Build Button and Ship It fit
Everything above is doable by hand, and you should understand it before automating it. If you would rather not maintain the script, Dedicated Server Build Button is a free editor tool from this publisher that does the manual path: one menu item per platform (Linux or Windows), the enabled Build Settings scenes, SERVER_BUILD defined through extraScriptingDefines, a fresh output folder, a run script, a zip without symbols, a dialog that names the missing Hub module, and a CLI entry point per platform with exit code 0 or 1. It targets Unity 6.3 LTS and newer and is submitted to the Asset Store after Ship It goes live.
Ship It (39.99 USD, in review as of 2026-10-29) adds build profiles as assets, a server frame-rate cap applied at startup, a build stamp with the git hash readable at runtime, SteamPipe upload, a Dockerfile for the Linux server and a GitHub Actions export.
Neither provides netcode or hosting. They build and package; the transport, the matchmaking and the machine the server runs on are still yours.
Sources
- https://docs.unity.com/en-us/engine/6000.3/manual/platform-specific/dedicated-server/introduction
- https://docs.unity.com/en-us/engine/6000.3/manual/platform-specific/dedicated-server/get-started
- https://docs.unity.com/en-us/engine/6000.3/manual/platform-specific/dedicated-server/get-started/requirements
- https://docs.unity.com/en-us/engine/6000.3/manual/platform-specific/dedicated-server/get-started/player-settings
- https://docs.unity.com/en-us/engine/6000.3/manual/platform-specific/dedicated-server/get-started/optimizations
- https://docs.unity.com/en-us/engine/6000.3/manual/platform-specific/dedicated-server/build
- https://docs.unity.com/en-us/engine/6000.3/manual/platform-specific/dedicated-server/desktop-headless-mode
- https://docs.unity.com/en-us/engine/6000.3/manual/unity-editor/command-line-arguments/editor
- https://docs.unity.com/en-us/engine/6000.3/manual/unity-editor/command-line-arguments/player
- https://docs.unity.com/en-us/engine/6000.3/manual/programming-environment/script-compilation/conditional-compilation/scripting-symbol-reference
- https://docs.unity.com/en-us/engine/6000.3/manual/platform-specific/linux/build-for
- https://docs.unity.com/en-us/engine/6000.3/manual/platform-specific/windows/standalone-binaries
- https://docs.unity.com/en-us/engine/6000.3/script-reference/unityeditor/buildplayeroptions
- https://docs.unity.com/en-us/engine/6000.3/script-reference/unityeditor/buildplayeroptions/subtarget
- https://docs.unity.com/en-us/engine/6000.3/script-reference/unityeditor/standalonebuildsubtarget
- https://docs.unity.com/en-us/engine/6000.3/script-reference/unityeditor/buildtarget
- https://docs.unity.com/en-us/engine/6000.3/script-reference/unityeditor/build/reporting/buildresult
- https://docs.unity.com/en-us/engine/6000.3/script-reference/unityeditor/editorapplication/exit
- https://docs.unity.com/en-us/engine/6000.3/script-reference/unityengine/application/targetframerate
- https://docs.unity.com/en-us/engine/6000.3/script-reference/unityengine/dedicatedserver/arguments/targetframerate
- https://docs.unity.com/hub/add-modules.html