Vintage Story Dedicated Server Setup on Linux
Vintage Story ships a separate server build. It is its own download, not the client with a flag, and it is not on Steam, so there is no steamcmd install and no app ID to chase. The server is a .NET application: install a runtime, unpack an archive, point the process at a data directory. Everything below is detail around those steps.
Linux first, because that is where most long-lived Vintage Story servers end up. Current stable at the time of writing is 1.22.7, verified against the official version feed in September 2026.
Server requirements, and what drives them
The .NET runtime is version specific
This is the most common reason a fresh server exits within a second of launching. Each build declares the .NET major version it needs.
| Server version | Required .NET runtime | Package on Debian and Ubuntu |
|---|---|---|
| 1.22.0 through 1.22.7 | .NET 10 | dotnet-runtime-10.0 |
| 1.21.x, including 1.21.6 and 1.21.7 | .NET 8 | dotnet-runtime-8.0 |
Every archive contains VintagestoryServer.runtimeconfig.json, and the framework version in it is authoritative for that build:
cat VintagestoryServer.runtimeconfig.json
# "tfm": "net10.0", "version": "10.0.0" -> install dotnet-runtime-10.0
Install the runtime, not the SDK; the server never touches the SDK.
RAM, and why the numbers look high
Vintage Story holds every loaded chunk in the .NET managed heap. The server keeps chunks open around each player out to MaxChunkRadius, which defaults to 12, so at that setting each player carries a footprint roughly 25 chunks square. Three things follow:
- Spread beats headcount. Eight players in one base load a single overlapping chunk set. Eight players in eight bases load eight nearly disjoint sets, and need far more RAM at the same player count.
- Entities in loaded chunks tick. Large animal pens and dense spawns cost CPU and memory in proportion to how much map is held open.
- Mods add a fixed floor. Assets load once at startup and stay resident, so a heavy pack can cost a gigabyte before anyone connects.
Know one .NET behaviour before filing a bug report: the garbage collector does not return memory to the operating system promptly, so resident size climbs for a few hours then sits high. Judge health by tick rate, not by resident size.
Figures to allocate for a 1.22 server, not a hard floor:
| Players and play style | Chunk radius | RAM to allocate |
|---|---|---|
| 1 to 4, vanilla, one shared base | 12 | 4 GB |
| 1 to 4, vanilla, scattered bases | 10 to 12 | 6 GB |
| 5 to 10, vanilla | 10 to 12 | 8 GB |
| 5 to 10, moderate mods | 10 | 12 GB |
| 10 to 20, vanilla | 8 to 10 | 12 to 16 GB |
| 10 to 20, heavy mods | 8 | 20 GB |
| 20 or more, modded | 6 to 8 | 24 GB and up |
The 1.22 line added river simulation and a reworked fishing and entity layer, so a world that sat comfortably on 1.21 wants headroom after the upgrade. For a large community, running a large Vintage Story server goes deeper.
CPU and storage
- CPU: the main simulation tick is effectively single threaded. Worldgen uses other threads, so cores help during exploration, but the tick rate players feel tracks single core clock speed. A fast four core chip beats a slow sixteen core chip here.
- Storage: worlds are SQLite databases with a
.vcdbsextension, and each autosave flushes the dirty chunk set into one. On spinning disks that is a periodic stutter, so use SSD or NVMe. A world of a few hundred hours reaches several gigabytes, and backups multiply it.
Where the server files come from
Builds are published per version, so you can pin one. That matters: client and server must match, and you often want to stay put until players and mods catch up. Three official endpoints:
| Endpoint | What it gives you |
|---|---|
https://api.vintagestory.at/lateststable.txt | Current stable version as a bare string, for example 1.22.7. Ideal for a scripted check. |
https://api.vintagestory.at/stable.json | Every stable version with per platform filenames, sizes, MD5 sums and download URLs. |
https://api.vintagestory.at/unstable.json | The same for the unstable line. |
The Linux server package sits under linuxserver:
https://cdn.vintagestory.at/gamefiles/stable/vs_server_linux-x64_1.22.7.tar.gz
Swap the version to pin any published stable build. The archive is about 51 MB. Downloads and your account live on the official Vintage Story site, and stable.json lists an account mirror per file.
One gotcha that bites everyone once. The archive has no top level directory. It unpacks assets, Lib, Mods and VintagestoryServer.dll straight into the current directory, so create the target directory first and extract with -C, or you scatter thousands of files across your home folder.
Installing on Linux, start to finish
Commands assume Debian 12, run as root.
1. Install the .NET runtime
wget https://packages.microsoft.com/config/debian/12/packages-microsoft-prod.deb -O /tmp/ms-prod.deb
dpkg -i /tmp/ms-prod.deb
rm /tmp/ms-prod.deb
apt-get update
apt-get install -y dotnet-runtime-10.0
dotnet --list-runtimes
On Ubuntu, replace debian/12 with your release path, such as ubuntu/24.04. The last command must print a Microsoft.NETCore.App 10.x line, or nothing below will work.
2. Create a dedicated user and two directories
adduser --system --group --home /opt/vintagestory --shell /usr/sbin/nologin vintagestory
mkdir -p /opt/vintagestory/server
mkdir -p /srv/vintagestory/data
Two directories, deliberately. The binaries directory is disposable and gets replaced on every update. The data directory holds world, mods, player data and configuration, and survives untouched. That split makes upgrades and rollbacks boring.
3. Download and unpack
cd /opt/vintagestory/server
VS_VERSION=1.22.7
wget "https://cdn.vintagestory.at/gamefiles/stable/vs_server_linux-x64_${VS_VERSION}.tar.gz" -O /tmp/vs_server.tar.gz
tar -xzf /tmp/vs_server.tar.gz -C /opt/vintagestory/server
rm /tmp/vs_server.tar.gz
chown -R vintagestory:vintagestory /opt/vintagestory /srv/vintagestory
4. First run, in the foreground
Run it by hand once before writing service files. The entry point is the DLL:
sudo -u vintagestory dotnet /opt/vintagestory/server/VintagestoryServer.dll --dataPath /srv/vintagestory/data
Without --dataPath the server falls back to ~/.config/VintagestoryData for whichever user launched it, which is how people lose worlds they were sure they saved. The useful arguments are few, since most settings live in the config file:
| Argument | Purpose |
|---|---|
--dataPath | Directory for world, mods, config, player data and logs. Always set this. |
--port | Listening port. Overrides the config value. Default 42420. |
--ip | Bind address, to pin the server to one interface. |
--maxclients | Player slot limit. Overrides the config value. |
First launch generates the world and pegs a core for a minute or two. Stop it with Ctrl+C, a clean shutdown.
What first boot creates
The data directory is now the whole operating surface of the server:
| Path in the data directory | Contents |
|---|---|
serverconfig.json | Every server setting. The file you edit most. |
servermagicnumbers.json | Engine tuning values. Leave alone. |
Saves/ | World databases. A fresh server creates default.vcdbs. |
BackupSaves/ | Automatic backups. Grows steadily, prune on a schedule. |
Mods/ | Server side mods, as zip files. |
Playerdata/ | Per player inventories, positions and roles. |
Logs/ | All server logs. |
Cache/ and RiverCache/ | Regenerable. Safe to delete while stopped. |
Settings to change before anyone joins
Stop the server and open serverconfig.json. It is plain JSON, so a trailing comma stops the boot and says so in server-main.log.
| Key | Why it matters on day one |
|---|---|
ServerName | What appears in the public server list. |
Password | Simplest gate for a private server. Empty means open. |
MaxClients | Slot limit. Match it to your RAM, not your hopes. |
MaxChunkRadius | Server side view distance, default 12. Strongest lever over RAM and CPU. |
Port | Default 42420. Change only for a second server on the host. |
Ip | Ships as *, all interfaces. An address binds one NIC. |
AutoSaveInterval | Seconds between autosaves, 300 by default. Shorter loses less, stutters more. |
AdvertiseServer | Whether to appear in the public list. |
WhitelistMode, OnlyWhitelisted | Whitelist enforcement. Enable on anything advertised. |
VerifyPlayerAuth | Validates accounts against the auth servers. Keep on. |
AllowPvP, AllowCreativeMode, AllowTeleportation | Set the social rules before players form expectations. |
CorruptionProtection | Integrity checking on chunk writes. Leave enabled. |
World generation settings such as climate, temporal storm frequency and ore density live under WorldConfiguration in the same file, and many only apply to a world that has not been generated yet. The server configuration guide works through the file key by key, and the worldconfig command reference covers what you can change on a live world.
Running it under systemd
Write /etc/systemd/system/vintagestory.service:
[Unit]
Description=Vintage Story Dedicated Server
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=vintagestory
Group=vintagestory
WorkingDirectory=/opt/vintagestory/server
ExecStart=/usr/bin/dotnet /opt/vintagestory/server/VintagestoryServer.dll --dataPath /srv/vintagestory/data
Restart=on-failure
RestartSec=15
KillSignal=SIGINT
TimeoutStopSec=180
[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable --now vintagestory
Two lines do the important work. KillSignal=SIGINT makes systemctl stop a clean shutdown rather than a kill: Vintage Story installs one handler for both signals, so SIGINT and SIGTERM take the same shutdown path; the setting that actually decides whether the world is saved is TimeoutStopSec, which must be long enough for the save to finish before systemd escalates to SIGKILL. TimeoutStopSec=180 lets a large world finish that save before systemd escalates.
For an interactive console, run the same command inside screen or tmux. The archive ships an example init script, server.sh, that does this.
Ports
Vintage Story listens on 42420 and needs both TCP and UDP. Opening only TCP is the classic half working setup: the server appears in the list, then connections hang.
ufw allow 42420/tcp
ufw allow 42420/udp
ufw reload
Several servers on one machine need one port and one data directory each: 42420, 42421, 42422. Behind a router, forward both protocols to the machine's LAN address. The full walkthrough, with firewalld, Windows Firewall, dynamic DNS and how to test a port from outside, is on the port configuration page, and the unable to connect guide covers what is left.
VPN and bind address traps. If outside players cannot reach a server you set up over a VPN, check Ip in serverconfig.json. A private VPN address means only VPN and LAN clients will ever connect; reset it to * to bind every interface. If that does not fix it, your ISP is probably using carrier grade NAT, and no amount of port forwarding will help.
Docker
There is no official Vintage Story server image, so you build your own. The server has no native dependencies beyond what the Microsoft runtime image carries.
FROM mcr.microsoft.com/dotnet/runtime:10.0
ARG VS_VERSION=1.22.7
RUN useradd --create-home --uid 1000 vintagestory
RUN mkdir -p /opt/vintagestory /data
ADD https://cdn.vintagestory.at/gamefiles/stable/vs_server_linux-x64_${VS_VERSION}.tar.gz /tmp/vs.tar.gz
RUN tar -xzf /tmp/vs.tar.gz -C /opt/vintagestory
RUN rm /tmp/vs.tar.gz
RUN chown -R vintagestory:vintagestory /opt/vintagestory /data
USER vintagestory
VOLUME ["/data"]
EXPOSE 42420/tcp 42420/udp
STOPSIGNAL SIGINT
ENTRYPOINT ["dotnet", "/opt/vintagestory/VintagestoryServer.dll", "--dataPath", "/data"]
docker build -t vintagestory:1.22.7 .
docker run -d --name vs -p 42420:42420/tcp -p 42420:42420/udp -v /srv/vintagestory/data:/data --stop-timeout 180 vintagestory:1.22.7
Three details separate a working container from one that quietly eats worlds:
- The data volume is the point. Everything mutable lives under
--dataPath, so one host directory at/datakeeps the world outside the container lifecycle and a version bump becomes a rebuild plus a restart. - Publish UDP as well as TCP. A bare
-p 42420:42420is TCP only, producing the half working symptom above. - Get the stop signal right. The half that matters here is
--stop-timeout. Docker's default is ten seconds, and a world that needs longer than that to write is killed mid-save. Raise the timeout to cover your save, and match the signal to whatever your entrypoint expects.
The container writes as UID 1000, so the bind mounted host directory must be writable by that UID, or the server cannot create Saves/.
Proxmox
Vintage Story needs no kernel modules, no device passthrough and no privileged capabilities, so an unprivileged LXC container is the right default on Proxmox and costs far less overhead than a full VM. A Debian 12 template, the RAM from the sizing table, two to four vCPU on SSD backed storage, and the Linux steps above run inside it unchanged. What catches people out is memory and snapshots:
- Turn off ballooning, or set the minimum equal to the target. The .NET heap grows and holds, so the balloon driver sees a guest that never gives memory back and keeps squeezing. The result is swapping inside the guest and stutter with no visible cause.
- If the host runs ZFS, cap
zfs_arc_max. The ARC will otherwise expand into the RAM you meant for the guest. - Snapshot with the server stopped. A live snapshot catches the SQLite database mid write. It will often restore fine and occasionally will not, and you find out only when you need it.
- Keep the data directory on a bind mount or its own dataset, so you can rebuild the container from the template without touching the world.
- Forward 42420 TCP and UDP through the Proxmox host firewall to the container IP if the container sits on an internal bridge.
Pick a VM over LXC only for hard tenant isolation, or a kernel the host lacks.
Stopping the server without corrupting the world
Chunk data is a live SQLite database, and killing the process mid write is the most reliable way to lose a world. In order of preference:
/stopin the server console. Saves and shuts down in order.systemctl stop vintagestory, with theKillSignal=SIGINTunit above.- SIGINT to the process, which is what
Ctrl+Csends in a foreground session. kill -9, only when the process is genuinely wedged. Expect to lose everything since the last autosave.
/autosavenow forces an immediate save, which is what you run before maintenance. /announce warns everyone online instead of dropping them without notice. The admin commands reference has the rest.
Before anything risky, stop and copy the world. Saves/default.vcdbs is a single file, so a backup is one cp. The save management guide covers rotation, restoring from BackupSaves/ and worlds that will not load.
Logs
Everything lands in Logs/ inside the data directory:
server-main.log: startup, shutdown, joins, leaves, config parse errors. Start here.server-debug.log: verbose engine detail and full exception traces.server-crash.log: written when the server dies from an unhandled exception.server-audit.log: administrative actions, useful after griefing.server-chat.log,server-worldgen.logandserver-build.log: chat, generation and build detail.
Under systemd, journalctl -u vintagestory -f streams the same output live, the fastest way to watch a start attempt fail. To read a crash file, see where Vintage Story stores server crash logs. For stutter rather than crashes, see the chunk loading lag fix.
Updating to a new version
The split between binaries and data is what makes updates safe. The shape never changes:
- Announce, then stop the server cleanly.
- Copy the data directory off the machine.
- Replace the binaries directory with the new version, extracted fresh rather than over the old one.
- Check whether the new build needs a different .NET major version.
- Confirm your mods have releases for the new version before starting back up.
- Start, watch
server-main.log, and only then tell players it is back.
Extracting over an old version leaves stale files behind, and the server refuses to run rather than mix two builds. Updating a Vintage Story server without losing your world covers rollback too, and since mods are where most upgrades go wrong, read mod related save corruption during an update first.
Mods, clients and Windows
Server side mods are zip files in Mods/, loaded at startup. Anything that changes content rather than just server behaviour must be on every client too, at the same version. Installing mods on a server covers load order and the console install commands. Still weighing options? LAN, VPN or hosted compares them and what a Vintage Story server costs puts numbers on it.
The Windows package is the same application: download it from your account page, extract, and launch VintagestoryServer.exe, which handles the .NET invocation for you. Same runtime rule, same data layout, same port rules, except the default data path sits under %appdata%. Migrating a Vintage Story server from Windows to Linux covers the path and permission differences.
When the server will not start
| Symptom | Most likely cause |
|---|---|
| Exits within a second, no log written | Wrong or missing .NET runtime. Compare dotnet --list-runtimes with runtimeconfig.json. |
| Refuses to run and mentions old files | A new build extracted over an old one. Delete the binaries directory and unpack cleanly. |
| Stops at startup with a config complaint | Invalid JSON in serverconfig.json. The line is named in server-main.log. |
| Starts, but no world where you expected | --dataPath omitted, so it used ~/.config/VintagestoryData. |
Cannot create Saves/ or write logs | Ownership mismatch. The service user must own the data directory. |
| Appears in the list, connections hang | UDP not open or not forwarded. TCP alone is not enough. |
| Version mismatch on connect | Client and server must match exactly, patch number included. |
| Killed by the OOM killer under load | Allocation too small for the player spread. Lower MaxChunkRadius or add RAM. |
For crashes specific to the current line rather than setup mistakes, 1.22 server stability issues collects the patterns we have seen.
Looking for managed Vintage Story server hosting? Supercraft runs Vintage Story dedicated servers on the current stable build with daily backups, instant setup and four region options, so the runtime version, the port rules and the clean shutdown on this page are already handled.