This post shows how to setup a full dedicated TF2 multiplayer server on a modern Linux system (including process supervising and some privilege separation). Additionally we install some addons and plugins to increase the functionality and comfort.

Team Fortress 2 is a multiplayer online shooter - that even though it's now more than 10 years old - still has a active and loyal community. The rate of updates has dropped to nearly zero, yet the community is still alive and loyal.

⚠️ Before we begin: This post has been a draft for multiple years now, as I started it as a reference for myself but was never happy enough to publish it. As TF2's time is slowly going to an end, I'm releasing it now to to fully waste all the effort in writing and researching this.

I'm using mostly Ubuntu 18.04 for this tutorial (and already updated parts for 20.04), as it's a LTS release which is still supported until 2023.

These instructions have been tested on a fresh Ubuntu Server installation, but should also work on existing systems. You need full root access to setup the base, the game server itself will run under it's own user account.

Note that most of the instructions can also be applied to other Source Engine based games like Garry's Mod, Counter Strike or Left 4 Dead (2).

Setting up the base server

Before installing the actual game server, it's necessary to setup the system and install some needeed packages.

Initial updates

Execute the following three commands on your systems. They update the cache of the package manager, upgrade any old packages and restart the system. The latter is required to apply any kernel updates.

Bash
apt update
apt full-upgrade --yes
reboot

Firewall

The following commands allow traffic for all needed ports for the server (SSH + Game Server) and put the firewall in a secure default mode.

Note: If you want to host the server on a different port or multiple servers, add more exceptions accordingly.

Note: We only allow game server connections using UDP. If you want to use RCON, it's more reliable and secure to use SourceMod for that (sm_rcon command).

Bash
ufw allow ssh
ufw allow 27015/udp
ufw default reject
ufw enable

Base packages

Now let's install some basic packages that are useful to us later. This is pretty much the default set I use when I start with a server.

Bash
apt install --yes build-essential wget curl vim unzip htop tree

Setup steamcmd and more packages

To ease the setup, the steamcmd command used to install game servers on dedicated machines has already been packaged for Ubuntu, so we can install it using the regular package manager.

Bash
# Enable 32bit packages
dpkg --add-architecture i386

# Install steamcmd and some packages needed for the Source Engine server
# Note that you need to confirm the Steam terms in this step

# Ubuntu 18.04
apt install gdb steamcmd lib32gcc1 lib32stdc++6 libstdc++6 libstdc++6:i386 libtinfo5:i386 libncurses5:i386 libcurl3-gnutls:i386 libstdc++6:i386 lib32z1 lib32ncurses5 libcurl4-gnutls-dev:i386 lib32tinfo5

# Ubuntu 20.04
apt install gdb steamcmd lib32gcc1 lib32stdc++6 libstdc++6 libstdc++6:i386 libtinfo5:i386 libncurses6:i386 libcurl3-gnutls:i386 libstdc++6:i386 lib32z1 lib32ncurses6 libcurl4-gnutls-dev:i386 lib32tinfo6

# Link steamcmd globally
ln -s /usr/games/steamcmd /usr/local/bin/steamcmd

Note: The documentation on which packages are exactly needed are rather sparse, so I added everything I could find - some might not bee needed anymore.

If some of the packages are missing in later Ubuntu versions, they often just increased the package's version nummer in the name (e.g. lib32ncurses5 -> lib32ncurses6). Searching for packages using apt-cache search can help finding those.

As one of the last steps as root, let's make a user account for our game server.

Bash
useradd --create-home --shell /bin/bash steam

Install MariaDB (MySQL fork)

Note: This step is actually optional, but recommended for SourceMod as it allows to store configuration information and bans into the database and provides easier and external (in the sense of scripts) access to it.

Bash
apt install mariadb-server mariadb-client

Now, let's setup a database user for SourceMod:

Please change the password to something actually secure!

Bash
echo "CREATE DATABASE sourcemod" | mysql -uroot
echo "CREATE USER 'sourcemod'@'localhost' IDENTIFIED BY 'change-me-or-kittens-will-be-sad';" | mysql -uroot
echo "GRANT ALL PRIVILEGES ON sourcemod.* TO 'sourcemod'@'localhost';" | mysql -uroot
echo "FLUSH PRIVILEGES;" | mysql -uroot

Install the actual game server

Everything that happens here must be executing using the steam user. For security reasons we do not want to ever run the game server as root, which would allow exploits to takeover the entire operating system. Use the following command to change your current the shell to it:

Bash
# -i gives us a proper shell
# -u specified the user we want to be
sudo -i -u steam

Now, let's install TF2 into a subdirectory. If you want to have multiple servers, change the installation path accordingly. In theory you can start multiple servers from the same installation, but it takes further configuration tweaks especially when using SourceMod, which are not discussed here.

Bash
# Install (or update) TF2 and validate all of it's files
steamcmd \
    +force_install_dir /home/steam/tf2/ \
    +login anonymous \
    +app_update 232250 validate \
    +quit

Note: The same command can be used later to update and validate the server and all it's files. Maybe put it into a bash script?

After this is finished, link a file to remove some warnings when the server starts up:

Bash
mkdir -p /home/steam/.steam/sdk32/
ln -s /home/steam/.steam/steamcmd/linux32/steamclient.so /home/steam/.steam/sdk32/steamclient.so

We're nearly done for the first part! Now let's start the server to see if it boots up:

Bash
/home/steam/tf2/srcds_run -debug +map ctf_2fort

If you can connect to the server using the game client, you made it!

Bash
# Enter in the game console.
# If you don't have it, set the "-console" launch parameter in Steam.
connect ip-or-name.of.your.server

If the server crashes or you cannot connect, make sure all required packages are installed and the firewall has a rule to allow the traffic (ufw status). Copying error messages in Google is also useful. 😉

Install Metamod + SourceMod

A Source Engine server by itself is just the bare minimum to run the game, but as admin of a custom server you generally want more that the regular game.

For this, we need Metamod:Source and SourceMod, which provides a scripting language for easier access to the Source Engine and a wide variety of plugins created by the community.

Note: The versions used in this tutorial are surely outdated when you read this, so please check the linked websites above to download the most recent versions.

Note: The cd at the beginning are important to ensure the files are extracted at the right location.

Bash
cd /home/steam/tf2/tf

wget https://mms.alliedmods.net/mmsdrop/1.11/mmsource-1.11.0-git1145-linux.tar.gz
tar xzf mmsource-1.11.0-git1145-linux.tar.gz
rm mmsource-1.11.0-git1145-linux.tar.gz

wget https://sm.alliedmods.net/smdrop/1.10/sourcemod-1.10.0-git6537-linux.tar.gz
tar xzf sourcemod-1.10.0-git6537-linux.tar.gz
rm sourcemod-1.10.0-git6537-linux.tar.gz

After this is done, start the server again and try the following commands in the server console:

Bash
meta list
sm plugins

Should output something like:

Code
meta list
Listing 4 plugins:
  [01] SourceMod (1.10.0.6537) by AlliedModders LLC
  [02] TF2 Tools (1.10.0.6537) by AlliedModders LLC
  [03] SDK Hooks (1.10.0.6537) by AlliedModders LLC
  [04] SDK Tools (1.10.0.6537) by AlliedModders LLC

sm plugins
SourceMod Plugins Menu:
    info             - Information about a plugin
    list             - Show loaded plugins
    load             - Load a plugin
    load_lock        - Prevents any more plugins from being loaded
    load_unlock      - Re-enables plugin loading
    refresh          - Reloads/refreshes all plugins in the plugins folder
    reload           - Reloads a plugin
    unload           - Unload a plugin
    unload_all       - Unloads all plugins

If both work, SourceMod has been installed successfully - and you can start adding plugins to your server. Examples for this will be show in the next chapter.

Configure SourceMod

Before going public with your server, you should at least modify the following files:

Bash
# Users with admin permissions (if not using the database).
vim /home/steam/tf2/tf/addons/sourcemod/configs/admins_simple.ini

# General server configuration. This file is not guaranteed to exist.
vim /home/steam/tf2/tf/cfg/server.cfg

# Sourcemod general configuration.
vim /home/steam/tf2/tf/cfg/sourcemod/sourcemod.cfg

For a reference of options for the server.cfg, check out this page in the TF2 Wiki.

Tip: To save and exit vim, press ESC once, then directly enter :wq (write + quit) and press ENTER to confirm.

Configure the server

Set up the Database

If you have installed and want to use MariaDB, this is the time to setup the schema and configure SourceMod to use your database.

In case you have multiple game servers and depending on your use cases, either share databases between MariaDB servers or create own databases for each. In general your database does not need a lot of resources (it's just for user checking and sometimes stats after all).

Note: Execute these commands as root, so you don't have to enter a MariaDB password. (Depending on your distribution, at least on Ubuntu...)

Bash
cat /home/steam/tf2/tf/addons/sourcemod/configs/sql-init-scripts/mysql/create_admins.sql | mysql -uroot sourcemod
cat /home/steam/tf2/tf/addons/sourcemod/configs/sql-init-scripts/mysql/clientprefs-mysql.sql | mysql -uroot sourcemod

Next, edit the file /home/steam/tf2/tf/addons/sourcemod/configs/databases.cfg and replace the default connection with the following code. Don't forget to replace the password with the one you've chosen when setting up the database.

Bash
vim /home/steam/tf2/tf/addons/sourcemod/configs/databases.cfg
Javascript
	"default"
	{
		"driver"			"mysql"
		"host"				"127.0.0.1"
		"database"			"sourcemod"
		"user"				"sourcemod"
		"pass"				"the-pw-you-had-to-change-to-prevent-sad-kittens"
	}

Also, enable the plugins needed to manage SourceMod admins using the database:

Bash
mv /home/steam/tf2/tf/addons/sourcemod/plugins/disabled/sql-admin-manager.smx /home/steam/tf2/tf/addons/sourcemod/plugins/
mv /home/steam/tf2/tf/addons/sourcemod/plugins/disabled/admin-sql-prefetch.smx /home/steam/tf2/tf/addons/sourcemod/plugins/

Start your server again and enter the following commands in the server console, to give yourself (or me, in the example) full admin permissions:

Bash
sm_sql_addadmin "Mitch" steam "STEAM_0:1:14340376" z 99
sm_reloadadmins

To find out your own Steam ID, go to Steam ID Finder, enter your profile URL and then use the value of steamID (see above for the right format).

You should now be able to open the admin menu in-game using either /sm_admin in the chat or sm_admin in the game console. I recommend to bind the latter to a key (e.g. bind p sm_admin)

The command sm_reloadadmins is always required after changing admins in the database, as SourceMod is caching all users in memory for performance.

Automatic start and reboots

Starting a server in a SSH shell (even with screen or byobu) is annoying and needs a lot of maintanance to work stable. A better and easier way is to use systemd, a process supervisor that is available in most modern Linux distributions. It also allows you auto-restart the server for updates and cleanup and provides additional security features to further tighten security.

To use it, we need to add a service definition to it's configuration directory and notify it about it. Additionally, we'll add a timer unit to restart the server daily, which makes sure any memory leaks won't survive for long and all updates (if they were any 😅) are applied at least daily.

Save as: /etc/systemd/system/tf2.service

Ini
[Unit]
Description=TF2 Dedicated Server
Documentation=https://developer.valvesoftware.com/wiki/Counter-Strike:_Global_Offensive_Dedicated_Servers
After=network.target

[Service]
WorkingDirectory=/home/steam/
User=steam
ExecStartPre=/usr/games/steamcmd +runscript /home/steam/tf2_ds.txt
ExecStart=/home/steam/tf2/srcds_run -norestart -console -game tf -usercon +ip 0.0.0.0 -nohltv +sv_pure 1 +map ctf_2fort +maxplayers 32 +sv_setsteamaccount SERVER_STEAM_ACCOUNT

TimeoutStartSec=0
Restart=always

[Install]
WantedBy=default.target

Heads up:

  • Replace SERVER_STEAM_ACCOUNT with your Steam server account. While this is optional for TF2, it allows players to keep their favorites if your server's IP changes for any reason.
  • Change the default map and maxplayers. My default is 32, as we sometimes run MvM (which requires it for the bots managed by the game).

Alternatively, you could create a bash script with the launch command and use it's path instead in the ExecStart line. This task is up to the reader.

Additional, you need to create the file /home/steam/tf2_ds.txt, which is the update script for steamcmd:

Bash
force_install_dir /home/steam/tf2
login anonymous
app_update 232250
quit

Now enable the service and start it:

Bash
systemctl enable /etc/systemd/system/tf2.service
systemctl start tf2

You can check the logs using the systemd journal:

Bash
journalctl -fu tf2

Depending on the server's performance and number of installed plugins, it can take up to a minute for the server to be fully ready for client connections.

Note that by starting the server using systemd, you cannot use the interactive console of the server. You can generally replace it with SourceMod's sm_rcon command after joining the server.

If you lack the permissions to do so (e.g. to re-add admins), stop the systemd service and start the server manually again, as done above.

Daily reboot for updates

Using the systemd configuration above, the server only updates itself when it's fully restarted using systemd, not by it's internal update system (which has other bugs).

Bash
# As root
crontab -e

Now add this line at the end, which restarts the server always at 03:00 in the night:

Crontab
0 3 * * * /usr/bin/systemctl restart tf2.service >/dev/null 2>&1

Ideally this would be a systemd timer unit, but it takes much more code to do the same thing.

FastDL

When you have custom resources on your server like maps or additional textures, the game uses by default it's own protocol to download these files to the client - and that's really slow (capped to 20 KiB/s according to some sources). Instead, we'll add a small webserver which will be used to deliver the file.

Bash
# Run as root. Installs a basic nginx and allows firewall access
apt install --yes nginx-minimal
ufw allow 80

Now create this file: /etc/nginx/sites-enabled.d/tf2-fastdl.conf

Nginx
server {
    listen 80;
    server_name name-of-your-server;

    # For security reason, we explicitly whitelist "safe" directories
    # You'll probably have to add more, depending on what you add.
    location /custom/ { root /home/steam/tf2/tf/custom/; autoindex off; }
    location /maps/   { root /home/steam/tf2/tf/maps/;   autoindex off; }
}

Then update your server.cfg and add the following lines:

Bash
sv_allowdownload 1
sv_downloadurl "http://name-of-your-server/"

Finally, restart all services to pick up the new configuration:

Bash
systemctl reload nginx.service
systemctl restart tf2.service

Why not HTTPS?
Cause the Source Engine doesn't support it...

Where to go from here

You should now have a working TF2 game server including SourceMod and a database for managing admins.

Depending on what the server is for, you might want to add additional SourceMod plugins.

Also, please don't contact me for support, as I'm no longer hosting TF2 servers. ;-)

Further resources / references

Post changelog

    1. May 2022
    • Updated SourceMod download list, added rm commands to Metamod/SourceMod instructions
    • Added gdb to installed system packages
    • Fixed a warning with the order of force_install_dir
    • Added example output of meta list and sm plugins