Enshrouded server runbook
Rebuild an Enshrouded dedicated server on Windows 11 from a bare install: SteamCMD, ports, an update script and a Windows service with a nightly restart.
Following this document from a bare Windows install reproduces the exact working configuration, including everything ThangorodrimControl.exe depends on.
Fixed paths and names (hard dependencies)
ThangorodrimControl.exe has these values compiled in as constants. The stack must be rebuilt with these exact paths and names, or the exe must be recompiled with new constants (see section 9).
| Item | Value |
|---|---|
| SteamCMD | D:\steamcmd\steamcmd.exe |
| Server install root | D:\enshrouded-server\ |
| Server executable | D:\enshrouded-server\enshrouded_server.exe |
| Server config | D:\enshrouded-server\enshrouded_server.json |
| World saves | D:\enshrouded-server\savegame\ |
| Update-time backup mirror | D:\enshrouded-server\savegame-backup\ |
| Update script | D:\enshrouded-server\update-enshrouded.bat |
| Windows service name | EnshroudedServer (NSSM) |
| Server display name | Thangorodrim |
| Game/query port | UDP 15637 |
| LAN address | 192.168.7.7 (barad-dur.arda) |
| External address | hostname:15637 (static public IP) |
No Steam account login or game purchase is required for the server — the dedicated server is free app 2278520, installed via SteamCMD anonymous login. It can run alongside the game client on the same machine and same account.
Install SteamCMD
- Download
steamcmd.zipfrom Valve: https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip - Extract to
D:\steamcmd\(not Program Files — it self-updates in place and wants write access to its own folder). - Run
steamcmd.exeonce; it self-updates and drops to aSteam>prompt. Typequit.
Install the server
Run:
D:\steamcmd\steamcmd.exe +force_install_dir D:\enshrouded-server +login anonymous +app_update 2278520 validate +quit
+force_install_dir must come before +login or it is silently
ignored. Download is ~8 GB. A fresh SteamCMD install sometimes fails
its first attempt with Failed to install app '2278520' (Missing configuration) — simply run the same command again; if it persists,
delete D:\steamcmd\appcache and retry.
This same command is the update mechanism after every game patch (client and server versions must match or joins fail).
Server configuration
enshrouded_server.json does not exist until first launch. Run
enshrouded_server.exe once, let it start, close it, then edit the
generated file. Settings in use:
"name": "Thangorodrim""ip": "0.0.0.0"— bind-all; leave as-is. Never put the public IP here (it is a bind address, not an advertisement address)."queryPort": 15637"slotCount": 16(16 is the game’s hard cap, same as in-game hosting)."gameSettingsPreset": "Default"— presets are Relaxed / Default / Hard / Survival. Setting"Custom"activates the individualgameSettingsblock instead. Difficulty lives in this config, not in the world save.userGroups— the password IS the group selector at join time; no usernames exist. Every group must have a unique password. An empty password makes that group joinable by anyone (never leave Guest blank — it hascanEditWorld). Groups in use: Admin (full rights, password private), Friend, Guest, Visitor, plus optionally a Builder group (canExtendBase: true,canKickBan: false).reservedSlots: 1on Admin guarantees a join slot when full.
Restart the server after any config edit — the file is read only at startup.
Ports and port forwarding
- Windows Firewall: inbound allow rule for UDP 15637 (or for
enshrouded_server.exe). - OpenWrt port forward: UDP, source zone WAN, external port 15637 → destination zone LAN, 192.168.7.7, internal port 15637. NAT loopback/reflection is on by default in OpenWrt and handles internal use of the external name.
- DDNS
hostname.provider.tldpoints at the static public IP - NOT CGNAT. - Players join via Steam → View → Game Servers → Favorites → add
hostname.provider.tld:15637(LAN players can use192.168.7.7:15637), or find “Thangorodrim” in the in-game browser, then enter their group’s password. Steam pins a favourite to the IP resolved at add-time — if DNS was wrong when added, remove and re-add. - Bandwidth: ~2 Mbit/s upload per connected player; the 200/200 fibre line with Cake SQM (A+ bufferbloat, ~190 Mbps shaped) is over-provisioned for a full 16 slots.
Update script
D:\enshrouded-server\update-enshrouded.bat, exactly:
@echo off
REM Update script for NSSM workflow — the GUI (or you) stops/starts the
REM service; this script only backs up the save and runs SteamCMD.
REM "< CON" attaches SteamCMD's stdin to the (hidden) console so it does
REM not abort with "Input redirection is not supported" when launched
REM from ThangorodrimControl. Harmless when run by double-click too.
robocopy D:\enshrouded-server\savegame D:\enshrouded-server\savegame-backup /MIR >nul
D:\steamcmd\steamcmd.exe +force_install_dir D:\enshrouded-server +login anonymous +app_update 2278520 validate +quit < CON
Two properties matter and must be preserved:
- The script does NOT stop or start the server. Process lifecycle belongs to the NSSM service; ThangorodrimControl’s Update button performs graceful stop → this script → start.
- The
< CONon the SteamCMD line is required. ThangorodrimControl launches the script in a hidden console; without< CON, SteamCMD aborts with “Input redirection is not supported”.
The robocopy /MIR keeps a single rolling pre-patch mirror in
savegame-backup — it is overwritten on every update run. To restore:
stop the service, copy savegame-backup contents over savegame,
start. Restore BEFORE running another update. Independently of this,
the game itself keeps 10 numbered rolling backup copies of the world
inside savegame at all times.
NSSM service
NSSM 2.24 (64-bit exe from nssm.cc, extracted from the zip’s win64
folder, on PATH or invoked by full path). All commands in an ELEVATED
prompt:
nssm install EnshroudedServer D:\enshrouded-server\enshrouded_server.exe
nssm set EnshroudedServer AppDirectory D:\enshrouded-server
nssm set EnshroudedServer AppExit Default Restart
nssm set EnshroudedServer AppExit 0 Exit
nssm set EnshroudedServer AppStopMethodConsole 30000
nssm start EnshroudedServer
What each choice does:
AppExit Default Restart+AppExit 0 Exit— restart the server automatically if it crashes (non-zero exit), but stay stopped after a clean shutdown (exit 0). This prevents NSSM racing the update script or the GUI’s Stop button.AppStopMethodConsole 30000— on service stop, NSSM sends a console Ctrl+C first and allows up to 30 s for the server to flush a final save and exit before escalating (WM_CLOSE → WM_QUIT → terminate). This is what makes everynet stopgraceful. NSSM 2.24’s default of 1500 ms is too short for a save flush.- The service is created AUTO_START, so the server comes up on every
boot, before login — this is the boot-start mechanism (Task
Scheduler is NOT used for startup). Verify:
sc qc EnshroudedServershowsSTART_TYPE : 2 AUTO_START.
Graceful-shutdown verification: the Steam connection log in
D:\enshrouded-server\logs ends a clean stop with a LogOff() /
AsyncDisconnect / Log session ended sequence. A hard kill shows the
log simply cutting off.
On NSSM 2.24, nssm dump does not exist; use nssm edit EnshroudedServer (GUI) or nssm get EnshroudedServer <param>.
Nightly 04:00 restart
One scheduled task, created from an elevated prompt:
schtasks /create /tn "Enshrouded Nightly Restart" /tr "cmd /c net stop EnshroudedServer & timeout /t 5 /nobreak & net start EnshroudedServer" /sc daily /st 04:00 /ru SYSTEM /rl HIGHEST
Runs as SYSTEM (inherent service-control rights, runs logged-in or
not). net stop goes through NSSM’s Ctrl+C sequence, so the nightly
bounce is as graceful as a manual one. Test on demand:
schtasks /run /tn "Enshrouded Nightly Restart". Any older tasks from
the pre-NSSM setup (“Enshrouded Server Auto Startup”, old restart
task) must be disabled or deleted — NSSM owns startup now.
ThangorodrimControl.exe
Native Win32 GUI, written in Go (pure standard library, no runtime
dependencies), source in main.go alongside the exe. Embedded
requireAdministrator manifest — double-clicking triggers UAC, which
service control requires.
Buttons:
- Start —
net start EnshroudedServer - Stop (graceful) —
net stop EnshroudedServer, waits and confirms STOPPED state - Restart (graceful) — stop, 3 s pause, start
- Update — graceful stop → runs the update .bat in a hidden console → start, checking SteamCMD output for success
Status line polls the Service Control Manager API every 30 seconds (plus at launch and after every button operation) and logs externally caused state changes (4 AM bounce, crash restarts). The exe is a control panel only: the service runs with or without it, and it schedules nothing.
Compiled-in constants (top of main.go): serviceName = "EnshroudedServer", updateBat = "D:\enshrouded-server\update-enshrouded.bat". If any path or the
service name changes, edit the constants and rebuild:
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags "-s -w -H windowsgui" -o ThangorodrimControl.exe .
with the rsrc-generated .syso (admin manifest) present in the
package directory. Three fixes are baked into the final version and
must not be regressed: runtime.LockOSThread() pinning the UI thread
(without it the window eventually hangs); the update script launched
with CREATE_NEW_CONSOLE + hidden window (SteamCMD stdin fix, paired
with the .bat’s < CON); service status via direct SCM API calls
rather than parsing sc query output.
Reboot / power-loss behaviour
Cold boot or hardware reboot: SCM starts the NSSM service
automatically before login; the server registers with Steam (visible
in favourites within a minute or two of boot). Combined with the
existing Wake-on-LAN setup, the machine can be woken remotely and the
server self-starts with no interaction. If the server process crashes,
NSSM restarts it (AppExit Default Restart). A clean stop (GUI, net stop, nightly task) stays stopped until deliberately started.
World save notes (migration reference)
World saves are slot-named static hexadecimals: world 1 = 3ad85aea,
world 2 = 3bd85c7d, world 3 = 38d857c4, etc. The dedicated server
loads slot 1 (3ad85aea). To import a client-hosted world: stop the
server; locate the world’s complete file set (base, _info, -index,
_info-index, matching timestamps) in %USERPROFILE%\Saved Games\Enshrouded or ...\Steam\userdata\<SteamID>\1203620\remote;
back up and clear the server’s savegame; rename the set to slot-1
names; copy in; start. Player characters are stored client-side and
are unaffected. Server name, groups, and difficulty live in the JSON
config, not the save, and survive migration.