This builds a Source engine dedicated server on a 64-bit Linux VPS: SteamCMD under a dedicated non-root user, the game, a login token so the server is actually listed, and a systemd unit. The steps are identical for CS2, Team Fortress 2, and Garry's Mod apart from the app number and the start command.
Commands are for Debian 12 and Ubuntu 22.04 or newer, with the RHEL family
equivalents where they differ. You need sudo and an SSH session, see
Connecting to your server.
Install it
Create a user for the server
sudo useradd -m -s /bin/bash steamThe server does not need root, and a game server running as root turns any add-on flaw into a whole-machine problem. Everything below happens in
/home/steam.Install the prerequisites
SteamCMD is a 32-bit binary. The TF2 and Garry's Mod servers are 32-bit too, so the 32-bit runtime libraries are needed on a 64-bit host in every case.
On Debian and Ubuntu:
sudo dpkg --add-architecture i386 sudo apt update sudo apt install -y curl tar ca-certificates lib32gcc-s1 lib32stdc++6 lib32z1On RHEL, AlmaLinux, and Rocky:
sudo dnf install -y curl tar glibc.i686 libstdc++.i686 zlib.i686Install SteamCMD
sudo -u steam -i mkdir -p ~/steamcmd && cd ~/steamcmd curl -sSL https://steamcdn-a.akamaihd.net/client/installer/steamcmd_linux.tar.gz | tar zx ~/steamcmd/steamcmd.sh +quitThe last line runs it once so it can self-update before you ask it to install anything.
Link steamclient.so
Source servers look for the Steam client library in fixed locations. Create both links, so the same account works for 32-bit and 64-bit servers:
mkdir -p ~/.steam/sdk32 ~/.steam/sdk64 ln -s ~/steamcmd/linux32/steamclient.so ~/.steam/sdk32/steamclient.so ln -s ~/steamcmd/linux64/steamclient.so ~/.steam/sdk64/steamclient.soA server that starts and then exits complaining it cannot find
steamclient.sois missing these.Download the game
Pick the app number for your game from Running a Source engine server. All of them install anonymously.
Counter-Strike 2:
~/steamcmd/steamcmd.sh \ +force_install_dir /home/steam/cs2 \ +login anonymous \ +app_update 730 validate \ +quitTeam Fortress 2:
~/steamcmd/steamcmd.sh \ +force_install_dir /home/steam/tf2 \ +login anonymous \ +app_update 232250 validate \ +quitGarry's Mod, plus the Counter-Strike: Source content it mounts for community maps:
~/steamcmd/steamcmd.sh \ +force_install_dir /home/steam/gmod \ +login anonymous \ +app_update 4020 validate \ +quit ~/steamcmd/steamcmd.sh \ +force_install_dir /home/steam/css \ +login anonymous \ +app_update 232330 validate \ +quitThese are large downloads. Keep the command, because the same one updates the server later.
Get a game server login token
Sign in to your Steam account and open the Steam game server account management page, under Steam's own support and developer pages. Create an account for the app number of your game, give it a memo you will recognise later, and copy the token it gives you.
Without this the server never registers with Valve's master server list and therefore never appears in the in-game browser. See Running a Source engine server for what else a token affects.
Treat the token as a credential. It goes on the command line, never into a config you might share.
Write server.cfg
Create
server.cfgin the game's owncfgdirectory. For CS2 that is/home/steam/cs2/game/csgo/cfg/server.cfg, for TF2/home/steam/tf2/tf/cfg/server.cfg, and for Garry's Mod/home/steam/gmod/garrysmod/cfg/server.cfg.hostname "My server" rcon_password "a-long-random-password" sv_password "" sv_lan 0Leave
rcon_passwordempty if you are not going to use the remote console. An empty value disables RCON, which is safer than a weak password on an open administrative channel.Write the start script
Create a
start.shin the install directory,chmod 700it, and put your token in it. For CS2:#!/usr/bin/env bash set -euo pipefail cd /home/steam/cs2 exec ./game/bin/linuxsteamrt64/cs2 -dedicated -console -usercon \ -port 27015 \ +game_type 0 +game_mode 1 \ +map de_dust2 \ +sv_setsteamaccount YOUR_TOKEN_HEREFor Team Fortress 2:
#!/usr/bin/env bash set -euo pipefail cd /home/steam/tf2 exec ./srcds_run -game tf -console -usercon -norestart \ -port 27015 \ +maxplayers 24 \ +map cp_dustbowl \ +sv_setsteamaccount YOUR_TOKEN_HEREFor Garry's Mod:
#!/usr/bin/env bash set -euo pipefail cd /home/steam/gmod exec ./srcds_run -game garrysmod -console -usercon -norestart \ -port 27015 \ +maxplayers 32 \ +gamemode sandbox \ +map gm_construct \ +sv_setsteamaccount YOUR_TOKEN_HEREStart it by hand once
/home/steam/cs2/start.shRead the console output rather than skimming it. It tells you whether the login token was accepted, which map loaded, and which port it bound. A server that is up but silently unlisted usually said so on this screen. Stop it with
Ctrl+Cwhen you are satisfied.
The launch parameters that matter
Source separates -parameters, which configure the engine and are set once at
launch, from +commands, which are console commands executed at startup.
| Parameter | What it does |
|---|---|
-dedicated | Runs CS2 as a dedicated server. Not used with srcds_run, which is already a server. |
-console | Runs headless in the terminal, with no window. |
-usercon | Enables RCON. Without it, the remote console is off no matter what rcon_password says. |
-port 27015 | The UDP game port, which is also the query port. |
-norestart | Stops srcds_run restarting the server itself after a crash. Use it when systemd owns restarts, so the two do not fight. |
+map | The map to load at boot. The server does not start playing until a map is loaded. |
+maxplayers | Slot count on the srcds_run games. |
+sv_setsteamaccount | Applies the game server login token. |
+game_type and +game_mode | The CS2 and CS:GO mode pair. 0 and 1 is competitive. |
+gamemode | The Garry's Mod gamemode to run. |
Garry's Mod has one extra: mounting a workshop collection with
+host_workshop_collection also needs a Steam Web API key passed as -authkey,
or the server cannot fetch the collection.
Run it under systemd
The remaining commands are run as yourself, not as steam. Type exit to leave
the server account first.
Create /etc/systemd/system/cs2.service, changing the name, description, and
paths for your game:
[Unit]
Description=CS2 dedicated server
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=steam
Group=steam
WorkingDirectory=/home/steam/cs2
ExecStart=/home/steam/cs2/start.sh
Restart=on-failure
RestartSec=10
TimeoutStopSec=30
[Install]
WantedBy=multi-user.targetThen:
sudo systemctl daemon-reload
sudo systemctl enable --now cs2
journalctl -u cs2 -fBecause the start script does not redirect output, everything the server prints lands in the journal, which is where you look when it will not stay up.
Updating is stop, SteamCMD, start:
sudo systemctl stop cs2
sudo -u steam /home/steam/steamcmd/steamcmd.sh \
+force_install_dir /home/steam/cs2 \
+login anonymous \
+app_update 730 validate \
+quit
sudo systemctl start cs2Open the ports
Nothing reaches the server until the ports are open at the network edge. For the start scripts above that is UDP 27015 for the game and its queries, plus TCP 27015 if you use RCON, and UDP 27020 if you enable SourceTV. Add a rule per port and protocol under Port rules, or apply the Source / CS2 entry in Game presets and add the rest yourself.
If the server is up but nobody arrives, work through When players cannot connect.