Installing a Source engine server on Linux

Installing a CS2, TF2, or Garry's Mod dedicated server with SteamCMD, applying a game server login token, and running it under systemd.

Game servers5 min read

On this page
  1. Install it
  2. The launch parameters that matter
  3. Run it under systemd
  4. Open the ports
  5. Related

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

  1. Create a user for the server

    Shell
    sudo useradd -m -s /bin/bash steam

    The 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.

  2. 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:

    Shell
    sudo dpkg --add-architecture i386
    sudo apt update
    sudo apt install -y curl tar ca-certificates lib32gcc-s1 lib32stdc++6 lib32z1

    On RHEL, AlmaLinux, and Rocky:

    Shell
    sudo dnf install -y curl tar glibc.i686 libstdc++.i686 zlib.i686
  3. Install SteamCMD

    Shell
    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 +quit

    The last line runs it once so it can self-update before you ask it to install anything.

  4. 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:

    Shell
    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.so

    A server that starts and then exits complaining it cannot find steamclient.so is missing these.

  5. Download the game

    Pick the app number for your game from Running a Source engine server. All of them install anonymously.

    Counter-Strike 2:

    Shell
    ~/steamcmd/steamcmd.sh \
      +force_install_dir /home/steam/cs2 \
      +login anonymous \
      +app_update 730 validate \
      +quit

    Team Fortress 2:

    Shell
    ~/steamcmd/steamcmd.sh \
      +force_install_dir /home/steam/tf2 \
      +login anonymous \
      +app_update 232250 validate \
      +quit

    Garry's Mod, plus the Counter-Strike: Source content it mounts for community maps:

    Shell
    ~/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 \
      +quit

    These are large downloads. Keep the command, because the same one updates the server later.

  6. 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.

  7. Write server.cfg

    Create server.cfg in the game's own cfg directory. 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 0

    Leave rcon_password empty 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.

  8. Write the start script

    Create a start.sh in the install directory, chmod 700 it, and put your token in it. For CS2:

    Shell
    #!/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_HERE

    For Team Fortress 2:

    Shell
    #!/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_HERE

    For Garry's Mod:

    Shell
    #!/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_HERE
  9. Start it by hand once

    Shell
    /home/steam/cs2/start.sh

    Read 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+C when 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.

The CS2 start command./game/bin/linuxsteamrt64/cs2-dedicated-console-usercon-port 27015+game_type 0+game_mode 1+map de_dust2+sv_setsteamaccount TOKEN-parametersConfigure the engine, set once at launch-dedicatedruns CS2 as a dedicated server-consoleheadless, in the terminal-userconenables RCON-port 27015the UDP game port, also the query port+commandsConsole commands, run at startup+game_type 0 +game_mode 1competitive+map de_dust2the map loaded at boot+sv_setsteamaccountapplies the login token
The CS2 start script on this page, sorted by kind. Everything with a minus configures the engine once, at launch; everything with a plus is a console command run at startup, the login token among them. Illustration of the start script above.
ParameterWhat it does
-dedicatedRuns CS2 as a dedicated server. Not used with srcds_run, which is already a server.
-consoleRuns headless in the terminal, with no window.
-userconEnables RCON. Without it, the remote console is off no matter what rcon_password says.
-port 27015The UDP game port, which is also the query port.
-norestartStops srcds_run restarting the server itself after a crash. Use it when systemd owns restarts, so the two do not fight.
+mapThe map to load at boot. The server does not start playing until a map is loaded.
+maxplayersSlot count on the srcds_run games.
+sv_setsteamaccountApplies the game server login token.
+game_type and +game_modeThe CS2 and CS:GO mode pair. 0 and 1 is competitive.
+gamemodeThe 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:

Config
[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.target

Then:

Shell
sudo systemctl daemon-reload
sudo systemctl enable --now cs2
journalctl -u cs2 -f

Because 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:

Shell
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 cs2
An update lands mid-eveningValve pushes an updateOn Valve's own schedule.Clients update immediatelyYour server refuses themStill on the older build. To players itlooks exactly like an outage.You update itstop, SteamCMD, startPlayers join again
Between Valve’s update and yours, nobody can join. Clients move to the new build at once, and the server refuses them until you run the stop, SteamCMD, start sequence above. Illustration.

Open 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.

Need a server, or a hand with one?

Pick a plan to deploy a server, or open a ticket in the panel. Support runs 24/7/365.