Enshrouded's dedicated server is a Windows program only — there is no native Linux build. If your always-on machine runs Linux, the realistic route is Proton, and the easiest way to get Proton right is the Docker image that bundles SteamCMD and Proton together. This page is that setup, at version 1.0.0.0, running on a home Linux box: the real compose file, the real config, the ports that matter, and the handful of gotchas that cost us the most time. Not a plan — a running server.
Why Linux + Proton
Keen ship enshrouded_server.exe, a Windows binary. You can run it on a Windows box, or under Proton on Linux. Ours lives on an always-on Linux server, so Proton it is. The image mbround18/enshrouded-docker:proton-latest does the heavy lifting: on first start it runs SteamCMD to pull the Windows depot of app 2278520 (about 8.6 GB of .exe and .dat files), then launches enshrouded_server.exe inside a Proton prefix.
The compose file
This is the whole thing — no secrets in it, the passwords live in enshrouded_server.json:
services:
enshrouded:
image: mbround18/enshrouded-docker:proton-latest
container_name: enshrouded-server
restart: unless-stopped
stop_grace_period: 120s # the server needs time to write the world on shutdown
ports:
- "15636:15636/tcp"
- "15636:15636/udp"
- "15637:15637/tcp"
- "15637:15637/udp"
- "13000:3000/tcp" # image health endpoints (/live /ready /health); 3000 is taken on this host
environment:
TZ: "America/New_York"
NAME: "Your Server Name"
IP: "0.0.0.0"
GAME_PORT: "15636"
QUERY_PORT: "15637"
SLOT_COUNT: "16"
UPDATE_ON_START: "false" # set true to let SteamCMD update on boot
AUTO_UPDATE: "false"
SCHEDULED_RESTART: "false"
STOP_DELAY: "120"
PROTON_NO_FSYNC: "1" # force Proton off the fsync/esync path
PROTON_NO_ESYNC: "1"
HTTP_PORT: "3000"
volumes:
- /srv/media/enshrouded-game/enshrouded:/home/steam/enshrouded # game files + world
- /srv/media/enshrouded-game/proton:/home/steam/.proton # persist the Proton prefix
- /srv/media/enshrouded-game/proton_data:/home/steam/.proton_data
Ports — the one that matters
- 15637 / UDP — the server port (
queryPort). With Update #2 the server uses a single UDP
port, and the 1.0 readme calls queryPort "the port for the server." This is the one to forward.
- 15636 / UDP — legacy game port. We map it too for older tooling; it is harmless but a forward
for it does nothing on 1.0.
- 13000 / TCP — the image's health endpoints, remapped from 3000 because another app on this
host already owns 3000. Not needed for players.
Firewall: allow 15637/udp. For players outside your LAN you also need a router port-forward of 15637/udp to the host; LAN and VPN players reach it directly.
The config: enshrouded_server.json
It sits next to enshrouded_server.exe, is plain JSON, and is read only at boot — every change is a restart. The 1.0 readme (shipped as enshrouded_server_readme.txt) documents every key. The ones that matter:
| Key | What it does |
|---|---|
name | the server name shown in the Join list |
saveDirectory / logDirectory | where the world and logs are written (default ./savegame, ./logs) |
ip | leave at 0.0.0.0 unless you have a reason; a public IP here stops it starting |
queryPort | the single server port, default 15637 |
slotCount | max players, 1–16 |
gameSettingsPreset | Default, Relaxed, Hard, Survival or Custom |
userGroups | roles (Admin / Friend / Guest / Visitor) with per-role passwords and permissions |
flameLevelFogRemoval | new in 1.0 — Flame level needed to place altars in the Shroud, default 11 |
Two things people get wrong: the top-level "password" key is legacy (it quietly builds a "default" group around it) — set passwords per role under userGroups instead. And there is no gamePort key that does anything; queryPort is the only port the server reads.
A trimmed, password-free shape:
{
"name": "Your Server Name",
"saveDirectory": "./savegame",
"logDirectory": "./logs",
"ip": "0.0.0.0",
"queryPort": 15637,
"slotCount": 16,
"gameSettingsPreset": "Default",
"userGroups": [
{ "name": "Admin", "password": "pick-a-strong-one", "canKickBan": true, "canAccessInventories": true, "canEditWorld": true, "canEditBase": true, "canExtendBase": true, "reservedSlots": 0 },
{ "name": "Friend", "password": "another-strong-one", "canKickBan": false, "canAccessInventories": true, "canEditWorld": true, "canEditBase": true, "canExtendBase": false, "reservedSlots": 0 },
{ "name": "Guest", "password": "one-more", "canKickBan": false, "canAccessInventories": false, "canEditWorld": true, "canEditBase": false, "canExtendBase": false, "reservedSlots": 0 }
]
}
The gotchas that actually bit us
- Host port 3000 was already taken — so the image's health endpoint moved to 13000. If you skip
this, the container starts but its /health is unreachable, which looks like a broken server.
- The Proton prefix was being rebuilt on every recreate — mount
.protonand.proton_data
to real directories, or it reprovisions for minutes on every restart.
- The launcher hardcodes WINEESYNC/WINEFSYNC — Proton's own script honours
PROTON_NO_FSYNC=1
and PROTON_NO_ESYNC=1, so set both to force it off that path.
- A too-fast shutdown corrupts or loses progress — the server writes the world on exit. Hence
stop_grace_period: 120s and STOP_DELAY=120. Never docker kill it.
- There is no console and no remote API — a server is administered by editing
enshrouded_server.json and restarting. Everything below is a restart.
- Updates: we run staged 1.0 files with
UPDATE_ON_START=false/AUTO_UPDATE=false. Set them
true to let SteamCMD pull patches on boot.
Where everything lives
| Path | What |
|---|---|
enshrouded/ | the game files (enshrouded_server.exe, ~8.6 GB of .dat) and enshrouded_server.json |
enshrouded/savegame/world0.ksd | the whole world |
enshrouded/savegame/backup/ | the image's rolling backups |
enshrouded/logs/ | server logs |
proton/, proton_data/ | the Wine/Proton prefix |
Backups
world0.ksd under savegame/ is the entire world. Copy savegame/ before any update or config change, and keep a copy before any world-file surgery — we once had to migrate a save slot from a legacy hex id to world0.ksd, and a pre-migration copy is what made that safe.
First session — what actually happened
We ran it with two players the night it went up — an adult (Magius) and a kid (jacksontutterow40) — with no changes to the default config. The server log tells the story:
- Both connected and played. Two player slots filled, replication reporting
OperatingNormally. - Disconnects were clean. On exit the server sent a final client save to each player, received
the ack, then removed them — and world0.ksd updated on save, so nothing was lost.
- Latency was low — 11–35 ms ping throughout, consistent with a same-network session.
- One harmless warning on each disconnect: `[session] Remove player from session. Not found in
online game info`. It appears at teardown and does not touch the save — don't chase it.
So the join flow, the password/role handling under userGroups, and the shutdown save all worked first time, with no workarounds. The part still unverified from here is connecting from outside the network (a router forward of 15637/udp) — that gets filled in the first time someone joins remotely.
Sources
- The official
enshrouded_server_readme.txtshipped with the server files, version 1.0.0.0 —
the authoritative reference for every config key.
- The Docker image:
mbround18/enshrouded-docker.
Guide changelog
What has changed in this guide, newest first.
- 2026-10-17Published from a live 1.0 server (Docker + Proton) with a first-session writeup: two players, clean save, 11–35 ms.