RE:NODE

Install a Valheim server on Linux with SteamCMD

By RE:NODE Team
13 min read


A Valheim dedicated server on Linux is four steps: install SteamCMD, create a user that owns nothing but the server, download app 896660 with an anonymous login, and start valheim_server.x86_64 through a copy of the bundled start_server.sh, which sets LD_LIBRARY_PATH to the server's own linux64 folder and SteamAppId to 892970. Open UDP 2456 and 2457, wrap the script in a systemd unit that stops the server with SIGINT so it saves on the way out, and you have a server that survives reboots. The whole thing takes about fifteen minutes on a fresh Ubuntu or Debian machine, and most of the trouble people have comes from three places: running it as root, editing the script Steam overwrites, and killing the process instead of stopping it.

This post is the install. What the launch arguments mean is in the Valheim launch arguments reference, and the bigger picture - sizing, admins, world modifiers - is in the Valheim dedicated server guide.

What you need before you start#

Valheim's server is a 64-bit Linux build of a Unity game. It runs on any current x86-64 distribution; Ubuntu 22.04 and 24.04 and Debian 12 are what most people use and what the commands below assume. ARM machines (a Raspberry Pi, Ampere cloud instances) cannot run it natively - SteamCMD itself is x86 only, and emulating the server through Box64 is a hobby project, not a way to host a group.

ResourceMinimumComfortable for 10 players
RAM2 GB free4 GB free
CPU1 fast core2 cores, high clock
Disk3 GB for the install10 GB with worlds and backups
NetworkUDP 2456-2457 reachableSame, low latency to players

Memory is the figure people get wrong in the other direction: the server itself is modest, but it grows with the explored world and the number of build pieces. How much RAM a Valheim server needs has the numbers by world age and group size. On a small VPS, remember that the operating system and SteamCMD take their share too; a 2 GB machine with nothing else running is fine for three friends and tight for ten.

You also need sudo on the machine and a terminal you are comfortable in. If the box is brand new, do the basics first - a non-root login, SSH keys, updates - which the first hour on a new VDS walks through.

Installing SteamCMD on Ubuntu and Debian#

SteamCMD is Valve's command-line Steam client. Both distributions package it, but it lives in the non-free part of the archive and needs 32-bit libraries, so two preparatory steps come first.

On Ubuntu, enable the multiverse component and the i386 architecture:

bash
$ sudo add-apt-repository multiverse$ sudo dpkg --add-architecture i386$ sudo apt update$ sudo apt install steamcmd

On Debian 12 the package is in non-free. Add contrib non-free to the components in your sources (in /etc/apt/sources.list, or the Components: line of /etc/apt/sources.list.d/debian.sources on newer installs), then:

bash
$ sudo dpkg --add-architecture i386$ sudo apt update$ sudo apt install steamcmd

The installer asks you to accept the Steam licence agreement; use the arrow keys and Enter. The binary ends up at /usr/games/steamcmd, which is on the default PATH for normal users but not always inside scripts and systemd units, so use the full path anywhere that is not an interactive shell.

A few libraries are worth installing at the same time. The crossplay backend loads libatomic1, and without it a server started with -crossplay fails during start-up with a missing-library error. libpulse0 is commonly reported as needed on minimal images as well, because the Unity runtime links against it even with no audio. Neither does any harm:

bash
$ sudo apt install libatomic1 libpulse0 ca-certificates

If you would rather not use the distribution package, Valve's tarball works too - download steamcmd_linux.tar.gz, unpack it into the server user's home directory and run ./steamcmd.sh instead of steamcmd. The commands are identical. The general mechanics - what +force_install_dir does, why validate exists, how beta branches work - are in SteamCMD explained.

A dedicated user for the server#

Do not run the server as root, and do not run it as your own login. A dedicated account with no sudo keeps a compromised or misbehaving server inside its own home directory, makes file ownership predictable, and means the world files never end up owned by root after somebody ran the script with sudo once.

bash
$ sudo useradd --create-home --shell /bin/bash valheim$ sudo -iu valheim

Everything from here until the systemd section happens as that user. The layout this post uses:

PathWhat lives there
/home/valheim/serverThe server install from SteamCMD
/home/valheim/server/start_valheim.shYour copy of the start script
/home/valheim/.config/unity3d/IronGate/ValheimDefault save directory: worlds and list files
/home/valheim/SteamSteamCMD's own cache and logs

The save directory is the one that matters. Unless you pass -savedir, the server writes worlds to worlds_local under ~/.config/unity3d/IronGate/Valheim of whichever user runs it, and the adminlist.txt, bannedlist.txt and permittedlist.txt files sit one level up. Run the server as a different user once and it will happily create a brand-new world in that user's home, which is the single most common "my world disappeared" report on Linux. Where Valheim keeps its files maps every path on both platforms.

Downloading app 896660#

Valheim's dedicated server is a separate Steam app from the game. The server is 896660; the game client is 892970. The server downloads with an anonymous login - you do not need to own Valheim or give SteamCMD any account.

bash
$ /usr/games/steamcmd +force_install_dir /home/valheim/server \    +login anonymous +app_update 896660 validate +quit

The order matters: +force_install_dir has to come before +login, and current SteamCMD prints a warning if you reverse them. The first run updates SteamCMD itself, which can take a minute and print a wall of progress, then downloads around 1-2 GB of server files. Success ends with:

code
Success! App '896660' fully installed.

If you see Error! App '896660' state is 0x202 after update job, the disk is full; 0x602 and similar codes usually mean a permissions problem on the install directory or an interrupted download. Run the same command again first - SteamCMD resumes - and check df -h and the directory owner if it repeats.

The same command is also the update command. Run it whenever Valheim patches, with the server stopped. validate checks every file against the manifest, which is slower but repairs anything a crash or a careless edit damaged; it will also overwrite your edits to files that ship with the server, which is exactly why the next section tells you to copy the start script. Update timing for a group - and what the Incompatible version error means when you get it wrong - is covered in updating a Valheim server.

start_server.sh, LD_LIBRARY_PATH and SteamAppId#

The install includes a sample start script. Its contents, give or take the exact comments and whether the sample line already includes -crossplay (it depends on the build), look like this:

start_server.sh (as shipped)
#!/bin/bashexport templdpath=$LD_LIBRARY_PATHexport LD_LIBRARY_PATH=./linux64:$LD_LIBRARY_PATHexport SteamAppId=892970echo "Starting server PRESS CTRL-C to exit"# Tip: Make a local copy of this script to avoid it being overwritten by steam.# NOTE: Minimum password length is 5 characters & Password cant be in the server name.# NOTE: You need to make sure the ports 2456-2458 is being forwarded to your server./valheim_server.x86_64 -name "My server" -port 2456 -world "Dedicated" -password "secret"export LD_LIBRARY_PATH=$templdpath

Two lines in it are doing real work, and both are why starting valheim_server.x86_64 on its own fails.

LD_LIBRARY_PATH=./linux64 tells the dynamic linker to look in the server's own linux64 folder, which contains the steamclient.so the server needs to talk to Steam. Without it you get [S_API FAIL] SteamAPI_Init() failed and a server that never registers. Note the path is relative: the script only works when run from the install directory, which is why the systemd unit below sets WorkingDirectory.

SteamAppId=892970 tells the Steam API which game this is. Yes, it is the client's app id, not the server's - that is how Valheim's server identifies itself to Steam, and changing it to 896660 breaks things.

The comment in the script is the advice to follow: make a copy. validate and every update restore start_server.sh to the shipped version, so edits to it vanish on the next patch. Your own copy, with your own name, survives:

/home/valheim/server/start_valheim.sh
#!/bin/bashcd "$(dirname "$0")"export LD_LIBRARY_PATH=./linux64:$LD_LIBRARY_PATHexport SteamAppId=892970exec ./valheim_server.x86_64 -nographics -batchmode \    -name "Longship Crew" -port 2456 -world "Midgard" \    -password "herring-barrel-42" -public 1 \    -saveinterval 900 -backups 4
bash
$ chmod +x /home/valheim/server/start_valheim.sh

Three changes from the sample are deliberate. cd "$(dirname "$0")" makes the relative library path work wherever the script is called from. -nographics -batchmode are standard Unity flags for a headless process; the sample sometimes omits them, and the server runs without, but they stop the Unity runtime from trying to initialise graphics on a machine that has none. And exec replaces the shell with the server process, so the SIGINT that systemd or Ctrl+C sends goes straight to Valheim instead of to a shell that may not pass it on. Skip the templdpath dance - it only matters when the script is sourced, not run.

The first start, and what the log should say#

Run the script by hand the first time, as the valheim user, so you can watch it:

bash
$ cd /home/valheim/server$ ./start_valheim.sh

The first start of a new world takes longer than any later one, because the world is generated and saved. Lines worth recognising, in roughly the order they appear:

code
Valheim version: l-0.xxx.xx (network version xx)Load world: Midgard (Midgard)Game server connectedSession "Longship Crew" with join code 483920 and IP 203.0.113.10:2456 is active

The version string will differ by patch; the shape is what matters. Load world with your world name confirms the -world argument. If you expected an existing world and instead see messages about generating one, the name does not match the file in worlds_local - capitals included. Game server connected means the Steam registration worked; if it never appears, look further up for SteamAPI_Init errors, which mean the library path or SteamAppId is wrong. A join code appears only with -crossplay.

Stop it with Ctrl+C and wait. You should see the world save before the process exits. That save-on-SIGINT behaviour is the reason everything later in this post is arranged the way it is. If the server does not start at all, Valheim server crashes on start goes through the failures by log message.

Firewall and ports#

Valheim uses UDP 2456 for game traffic and UDP 2457 for the Steam query, and the sample script's comment mentions 2456-2458. Opening the three-port range costs nothing and covers older builds that used the third:

bash
$ sudo ufw allow 2456:2458/udp comment 'Valheim'$ sudo ufw status

Note the colon in ufw's range syntax, and the /udp: a TCP rule does nothing for Valheim, and it is the most common reason a server runs fine but nobody can find it. On a cloud VPS there is often a second firewall in the provider's control panel (a security group) that needs the same rule. At home, forward the same UDP range on the router to the machine's LAN address. The ufw firewall guide covers the defaults to set before enabling ufw on a remote box - enable it without allowing SSH first and you lock yourself out.

If you change -port, everything shifts with it: the query port is always the game port plus one. Running two servers on one machine means two non-overlapping ranges, such as 2456-2458 and 2466-2468. The detail is in Valheim ports 2456-2458 explained.

Test from outside your network: join by IP in the game with your.address:2456. If that works and the server is still missing from the community list, the server is fine and the list is slow - see Valheim server not showing in the list.

Running it as a systemd service#

A server started from an SSH session dies when the session does. tmux or screen keep it alive but not across a reboot, and neither restarts it after a crash. A systemd unit does both, and - set up correctly - stops it cleanly.

/etc/systemd/system/valheim.service
[Unit]Description=Valheim dedicated serverWants=network-online.targetAfter=network-online.target[Service]Type=simpleUser=valheimGroup=valheimWorkingDirectory=/home/valheim/serverExecStartPre=/usr/games/steamcmd +force_install_dir /home/valheim/server +login anonymous +app_update 896660 +quitExecStart=/home/valheim/server/start_valheim.shKillSignal=SIGINTTimeoutStopSec=120Restart=on-failureRestartSec=15[Install]WantedBy=multi-user.target
bash
$ sudo systemctl daemon-reload$ sudo systemctl enable --now valheim$ journalctl -u valheim -f

The lines that matter:

  • KillSignal=SIGINT makes systemctl stop behave like Ctrl+C, so the world saves. The default, SIGTERM, is not guaranteed to trigger the same clean shutdown, and when the timeout expires systemd sends SIGKILL, which saves nothing.
  • TimeoutStopSec=120 gives a large world time to finish writing. The default of 90 seconds is usually enough; raising it costs nothing.
  • ExecStartPre updates the server on every start, so a restart after a Valheim patch brings you current. Drop that line if you run BepInEx mods - an automatic update on patch day breaks mods before their authors have caught up, and you want to choose that moment yourself. It also makes every start slower by the time a SteamCMD check takes.
  • Restart=on-failure brings the server back after a crash, but not after a deliberate stop.

Output goes to the journal, so journalctl -u valheim is the server log, searchable with --since and -g. If you prefer a file, add -logFile /home/valheim/valheim.log to the start line instead. systemd services for your apps covers units in more depth, including resource limits with MemoryMax=.

For a weekly restart, a timer or a root cron entry running systemctl restart valheim at a quiet hour is enough. Restarting clears the memory growth of a long-running world and, with the ExecStartPre line, picks up updates at the same time.

Backups the install does not give you#

The server's -backups flag keeps rotating copies of the world next to the world, on the same disk. It is protection against a bad save, not against a dead disk or a deleted directory. Copy the whole save directory somewhere else on a schedule:

bash
$ tar -czf /backup/valheim-$(date +%F).tar.gz \    -C /home/valheim/.config/unity3d/IronGate Valheim

Running that from cron while the server is up can catch the world mid-save; the safer pattern is to copy the most recent complete .db and .fwl pair, or to run the archive right after a scheduled restart. The full strategy, including restoring and spotting a corrupt world, is in Valheim server backups and world corruption.

If you would rather not run any of this yourself, a panel host does the same thing behind a form: on RE:NODE the Startup tab holds the arguments from your start line, both UDP ports are allocated with the plan, Restart sends a clean stop so the world saves, and backups go off the machine and restore with a button.

FAQ#

Do I need to own Valheim to download the server?

No. App 896660 downloads with +login anonymous. You need a copy of the game to play on the server, not to host it.

Why does the server need SteamAppId 892970 when its own app id is 896660?

The server uses the Steam API on behalf of the game, and the API identifies the game by the client's id. 896660 is only the depot SteamCMD downloads. Set SteamAppId to anything else and Steam registration fails.

Can I edit start_server.sh directly?

You can, but the next app_update ... validate or game patch will put the original back and your settings with it. Keep a copy under a different name and start that.

Should I run the server inside screen or tmux?

Only for a quick test. A multiplexer keeps the process alive after you log out but does nothing after a reboot or a crash, and it is easy to kill the session instead of stopping the server. A systemd unit with KillSignal=SIGINT is the setup that does not lose saves.

How do I add admins on a Linux server?

Put each player's ID on its own line in adminlist.txt in the save directory, ~/.config/unity3d/IronGate/Valheim for the valheim user by default. The file is re-read while the server runs. The ID formats, including crossplay prefixes, are in the Valheim list files guide.

Does the Linux server perform worse than the Windows one?

Not in any way a group will notice. Both are the same Unity build for different platforms, and Linux has less background overhead on a small machine. Linux vs Windows game servers covers the cases where the platform does matter.