Linux dedicated server
![]() | This guide assumes you know how to use the command line in Linux. |
This is a setup guide that allows you to install a basic Team Fortress 2 Linux dedicated server. In the below example the server is installed into the user's home directory in a directory called 'hlserver'.
Basic installation and configuration for a Linux dedicated server is the same for all distributions, but small details, such as package names, directories, etc. may differ from distribution to distribution. It is recommended that you check your distribution's repositories for the required packages before continuing. A link to a chef recipe based on this guide is also available in the "See also" section.
Contents
Reading this article
This article has been written according to standard Linux documentation syntax, meaning the following:
- A command prefixed by
#
(a hash) is meant to be run as root. Most distributions have a separate root account; for other distributions such as Ubuntu, Linux Mint, Crunchbang, etc., the sudo prefix to a command will ensure the command is run as root. - A command prefixed by
$
(a dollarsign) is meant to be run as a user. ./
means "current working directory".
Requirements
A number of requirements exist in order to run a Team Fortress 2 server on the Linux platform, namely:
- Write-access to the current working directory
glibc >= 2.3.6
- Approximately 4.49GB hard-drive space (for a barebones server with no custom maps, sounds, etc.)
- In the case of a 64-bit distribution, the package lib32gcc1 or ia32-libs is required to be installed:
On 64-bit CentOS run:
# yum install libgcc.i686 glibc.i686
On 64-bit Debian/Ubuntu/Linux Mint run:
$ sudo apt-get install lib32gcc1 ia32-libs
For x86_64 Arch Linux see here
- If you are installing on CentOS, you will have to use this command before
yum install ncompress
- If you are installing on FreeBSD (which is not Linux at all), you must enable Linux compatibility in the kernel and install a base system via the ports collection. Instructions to get you started are located here.
Download and install the SteamCMD Tool
It is recommended you create a separate user (such as "gameserver") to install and run game servers.
- Download steamcmd_linux.tar.gz
- Use tar to uncompress it to a directory, for example './hlserver' :
tar zxf steamcmd_linux.tar.gz
Create a steamcmd script
- Create a file called tf2_ds.txt in the folder where you install the tool, i.e. './hlserver'
- Edit the file with this text:
login anonymous force_install_dir ./tf2 app_update 232250 quit
If you want to opt in to the prerelease version of the TF2 dedicated server, add this line before the app_update line:
beta prerelease
Create an updating batch file
- Create a file called update.sh in the folder where you installed the tool, i.e. './hlserver'
- Edit the file with this text:
#!/bin/sh ./steamcmd.sh +runscript tf2_ds.txt
Start the download/update
- Run ./update.sh to start the download, may take several hours as dedicated server content is currently 4.4GB
- If however you get a permission denied error, execute this:
chmod +x update.sh
- You may need to run the file *multiple* times until the message "Success! App '232250' fully installed." displays.
Create/Update server files
- Create a file called server.cfg in '~/hlserver/tf2/tf/cfg'
- Edit the file with these details
hostname "Your_Servers_Name" rcon_password "Your_Rcon_Password" sv_contact "admin@yourdomain.com" mp_timelimit "30"
- Edit the files motd.txt, maplist.txt and mapcycle.txt all found in the '~/hlserver/tf2/tf' folder
Create a shell script to run the server
- Create a file in './hlserver/' called tf.sh
- Edit the file with this text:
#!/bin/sh tf2/srcds_run -game tf +sv_pure 1 +map ctf_2fort.bsp +maxplayers 24
- Run the file tf.sh to start your server
- Note: You could create an alias in your ~/.bashrc (or ~/.bash_aliases) file for this command as well.
Other command line options
Some other common command-line options for TF2 are:
+ip 1.2.3.4
- Bind to a specific IP. By default, listens on all network interfaces-port 27015
- Bind to a different port (27015 is the default)-strictportbind
- If a server is already running on the specified port, stop the server from running+randommap
- Use instead of +map to select a map at random+servercfgfile
- Which tf/cfg/ file to execute on map change, defaults to server.cfg+mapcyclefile
- Which tf/cfg/ file contains this server's mapcycle, defaults to mapcycle.txt. Will also look in tf/-replay
- Executes replay.cfg and adds an additional slot for the replay bot, but removes it from the player count.-autoupdate
- Attempts to auto-update the server when an update comes out. You MUST use -steam_dir and -steamcmd_script-steam_dir ~/hl1server/
- Location of steamcmd. Required by-autoupdate
-steamcmd_script ~/hl1server/tf2_ds.txt
- SteamCMD script to update this server. Required by-autoupdate
+sv_shutdown_timeout_minutes 360
- When autoupdating, automatically restart after this many minutes. Defaults to 360 (6 hours).
screen
Many server operators choose to run the server under screen
, as it allows you to switch between the console and the shell at will, as well as end your SSH session without killing the server. It can be installed via your distribution's package manager. The server can then be run with:
$ screen -m -S tf2 ./srcds_run parameters
where parameters is your normal srcds_run
parameters. You can use Ctrl+A+D to minimize the console, and screen -r tf2
to bring it back up. Ctrl+D will terminate the session (make sure you use the killserver console command first) and Ctrl+A, followed by Esc will allow you to scroll through it using ↑ and ↓. See the manual for more commands and parameters.
Automated startup at server boot
In the event of unexpected power outages, it might be useful to automatically restart your server. The following example Debian LSB init script can be used to automatically restart the server. Note that screen
is used. This script should be named tf2-server and can be put in the folder /etc/init.d. After doing that, run:
$ update-rc.d tf2-server defaults
This should ensure a proper startup of the server after each reboot.
#!/bin/sh ### BEGIN INIT INFO # Provides: tf2server # Required-Start: $remote_fs # Required-Stop: $remote_fs # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Team Fortress 2 server # Description: Starts a Team Fortress 2 server ### END INIT INFO NAME="Team Fortress 2" USER="tf2server" SCREENREF="tf2" BINARYPATH="/home/tf2server/hlserver/tf2" BINARYNAME="srcds_run" PIDFILE="tf2server.pid" OPTS="-game tf +sv_pure 1 +maxplayers 32 +ip 0.0.0.0 +map mvm_decoy -autoupdate -steam_dir /home/tf2server/hlserver/ -steamcmd_script /home/tf2server/hlserver/tf2_ds.txt +sv_shutdown_timeout_minutes 5" cd "$BINARYPATH" running() { if [ -n "`pgrep -f $BINARYNAME`" ]; then return 0 else return 1 fi } start() { if ! running; then echo -n "Starting the $NAME server... " start-stop-daemon --start --chuid $USER --user $USER --chdir $BINARYPATH --exec "/usr/bin/screen" -- -dmS $SCREENREF $BINARYPATH/$BINARYNAME $OPTS pgrep -f $BINARYNAME > $PIDFILE if [ -s $PIDFILE ]; then echo "Done" else echo "Failed" rm $PIDFILE fi else echo "The $NAME server is already started." fi } stop() { if running; then echo -n "Stopping the $NAME server... " kill `cat $PIDFILE` while running; do sleep 1 done rm $PIDFILE echo "Done" else echo "The $NAME server is already stopped." fi } case "$1" in start) start ;; stop) stop ;; restart) stop start ;; status) if running; then echo "The $NAME server is started." else echo "The $NAME server is stopped." fi ;; *) echo "Usage: $0 (start|stop|restart|status)" exit 1 esac exit 0
Alternatively, if you'd rather use an upstart script, paste the following into /etc/init/tf2-server.conf
description "TF2 Server" author "TF2 Wiki" start on runlevel [2345] stop on starting rc RUNLEVEL=[016] pre-start script cd /home/tf2-server/hlserver su -c "screen -dmS tf2 ./srcds_run -game tf +sv_pure 1 +maxplayers 32 +ip 0.0.0.0 +map mvm_decoy -autoupdate -steam_dir /home/tf2-server/hlserver/ -steamcmd_script /home/tf2-server/hlserver/tf2_ds.txt +sv_shutdown_timeout_minutes 5" tf2server while [ -z "$(pidof srcds_linux)" ]; do sleep 1 done end script script while [ -n "$(pidof srcds_linux)" ]; do sleep 1 done end script post-stop script fuser -k /home/tf2-server/hlserver/tf2/srcds_run while [ -n "$(pidof srcds_linux)" ]; do sleep 1 done end script
iptables rules
Firstly, run sudo -i
to get a root prompt. Then, enter this (all one line). To leave the root prompt and return to the standard user, just type exit
like normal.
iptables -A INPUT -i venet0 -p tcp -m tcp --dport 27015 -m state --state NEW,ESTABLISHED -j ACCEPT && iptables -A INPUT -i venet0 -p tcp -m tcp --dport 27005 -m state --state NEW,ESTABLISHED -j ACCEPT && iptables -A INPUT -i venet0 -p udp -m udp --dport 27015 -m state --state NEW,ESTABLISHED -j ACCEPT && iptables -A INPUT -i venet0 -p udp -m udp --dport 27005 -m state --state NEW,ESTABLISHED -j ACCEPT && iptables -A INPUT -i venet0 -p tcp -m tcp --dport 27016 -m state --state NEW,ESTABLISHED -j ACCEPT && iptables -A INPUT -i venet0 -p tcp -m tcp --dport 27017 -m state --state NEW,ESTABLISHED -j ACCEPT && iptables -A INPUT -i venet0 -p tcp -m tcp --dport 27018 -m state --state NEW,ESTABLISHED -j ACCEPT && iptables -A INPUT -i venet0 -p tcp -m tcp --dport 27019 -m state --state NEW,ESTABLISHED -j ACCEPT && iptables -A INPUT -i venet0 -p tcp -m tcp --dport 27020 -m state --state NEW,ESTABLISHED -j ACCEPT && iptables -A INPUT -i venet0 -p udp -m udp --dport 27016 -m state --state NEW,ESTABLISHED -j ACCEPT && iptables -A INPUT -i venet0 -p udp -m udp --dport 27017 -m state --state NEW,ESTABLISHED -j ACCEPT && iptables -A INPUT -i venet0 -p udp -m udp --dport 27018 -m state --state NEW,ESTABLISHED -j ACCEPT && iptables -A INPUT -i venet0 -p udp -m udp --dport 27019 -m state --state NEW,ESTABLISHED -j ACCEPT && iptables -A INPUT -i venet0 -p udp -m udp --dport 27020 -m state --state NEW,ESTABLISHED -j ACCEPT
CentOS 6.4 (minimal install)
on CentOS 6.4 the default iptables rules has a reject line as the last entry. This will cause all traffic to be rejected if you append (as the above commands recommend). To fix this I recommend first running the server to determine what ports it uses:
netstat -lnptu
This should output something like this:
(Not all processes could be identified, non-owned process info will not be shown, you would have to be root to see it all.) Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN - tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN - tcp 0 0 0.0.0.0:27015 0.0.0.0:* LISTEN 1393/./srcds_linux tcp 0 0 :::22 :::* LISTEN - tcp 0 0 ::1:25 :::* LISTEN - udp 0 0 0.0.0.0:27020 0.0.0.0:* 1393/./srcds_linux udp 0 0 0.0.0.0:26901 0.0.0.0:* 1393/./srcds_linux udp 0 0 0.0.0.0:68 0.0.0.0:* - udp 0 0 0.0.0.0:27005 0.0.0.0:* 1393/./srcds_linux udp 0 0 0.0.0.0:27015 0.0.0.0:* 1393/./srcds_linux
The ports listed with the ./srcds_linux name are being used by your server. In this instance port 27015 is using TCP, and ports 26901, 27005, 27015, and 27020 are using UDP. These ports need to be added to the iptables rules so that the firewall will not block access to incoming traffic.
To add them, first you should find out what is currently in your iptables config. You must do all these commands as root. The best practice is to add your account to the sudoers file so that you are somewhat restricted and access will time out. The choice is yours, su and login, or sudo
sudo iptables -nL --line-numbers
Which should output something like this:
Chain INPUT (policy ACCEPT) num target prot opt source destination 1 ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED 2 ACCEPT icmp -- 0.0.0.0/0 0.0.0.0/0 3 ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 4 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 state NEW tcp dpt:22 5 REJECT all -- 0.0.0.0/0 0.0.0.0/0 reject-with icmp-host-prohibited Chain FORWARD (policy ACCEPT) num target prot opt source destination 1 REJECT all -- 0.0.0.0/0 0.0.0.0/0 reject-with icmp-host-prohibited Chain OUTPUT (policy ACCEPT) num target prot opt source destination
Notice that line 5 says "REJECT all..." This means your server will reject everything. If you choose to append new rules to the iptables, you will still have no success with the server as the rules are followed in the order they appear in iptables. The commands you would like to run (again as root or sudo) are:
sudo iptables -I INPUT 5 -p tcp --dport 27015 -m state --state NEW,ESTABLISHED -j ACCEPT sudo iptables -I INPUT 6 -p udp --dport 27015 -m state --state NEW,ESTABLISHED -j ACCEPT sudo iptables -I INPUT 7 -p udp --dport 27005 -m state --state NEW,ESTABLISHED -j ACCEPT sudo iptables -I INPUT 8 -p udp --dport 27020 -m state --state NEW,ESTABLISHED -j ACCEPT sudo iptables -I INPUT 9 -p udp --dport 26901 -m state --state NEW,ESTABLISHED -j ACCEPT sudo service iptables save sudo service iptables restart
This will add the new rules above the reject line. If you look at the commands, they have a section that says "-I INPUT 5,6,7,8,9." This is telling what to make the rule number for this entry. Each time you add a new rule, you move the reject line down one, so you increment the rule number. You can choose to just keep adding the commands all as "-I INPUT 5". and everything will still work, but I like to keep my iptables nice and orderly. call me OCD. If you wanted, you could delete the REJECT LINE (sudo iptables -D INPUT 5) and re-add it later, but you have to get the syntax correct so it is easier to insert them above. The save will keep your iptables for reboot, and you must restart the service so the new rules are recognized. Your iptables should now look like this:
Chain INPUT (policy ACCEPT) num target prot opt source destination 1 ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED 2 ACCEPT icmp -- 0.0.0.0/0 0.0.0.0/0 3 ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 4 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 state NEW tcp dpt:22 5 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:27015 state NEW,ESTABLISHED 6 ACCEPT udp -- 0.0.0.0/0 0.0.0.0/0 udp dpt:27015 state NEW,ESTABLISHED 7 ACCEPT udp -- 0.0.0.0/0 0.0.0.0/0 udp dpt:27005 state NEW,ESTABLISHED 8 ACCEPT udp -- 0.0.0.0/0 0.0.0.0/0 udp dpt:27020 state NEW,ESTABLISHED 9 ACCEPT udp -- 0.0.0.0/0 0.0.0.0/0 udp dpt:26901 state NEW,ESTABLISHED 10 REJECT all -- 0.0.0.0/0 0.0.0.0/0 reject-with icmp-host-prohibited Chain FORWARD (policy ACCEPT) num target prot opt source destination 1 REJECT all -- 0.0.0.0/0 0.0.0.0/0 reject-with icmp-host-prohibited Chain OUTPUT (policy ACCEPT) num target prot opt source destination
This should fix your firewall issues.
Troubleshooting
- My server doesn’t show up in the LAN tab of my client’s server browser.
- I cannot access my loadout when playing on this server.
- Are you still able to connect using
connect <ip-address>
in the Developer Console? - During startup, does your server report the following?
* Unable to load Steam support library.* * This server will operate in LAN mode only.*
- When you connect with your client, does the server say the following?
NULL ISteamGameServer in ConnectClient. Steam authentication may fail. … Cannot verify load for invalid steam ID […]
- If so, then both of these problems have the same solution: Make sure you have a
steam_app.txt
file containing the number440
in yourorange box
folder. See Running the server, above.
- My server doesn’t auto-update on startup.
- When starting up, does your server say the following?
ERROR: -autoupdate requires -steam_dir and -steamcmd_script. WARNING: Failed to locate steam binary. WARNING: Could not locate steam binary:, ignoring.
- If so, then you need point
srcds_run
to the location of your SteamCMD binary, using the-steam_dir
argument and to your steamcmd update script using the-steamcmd_script
argument. See Other command line options, above.
- My server is stuck at 0/0 during updates
- When an update is running, does the console output the following?
force_install_dir /home/your/server/directory app_update 232250 validate Initial App state (0x4) installed App state (0x4) installed, progress: 0.00 (0 / 0) Success! App '232250' already up to date.
- If so, remove the "validate" instruction from the update command.
- My server doesn't work with rcon and/or refuses to broadcast UDP logs
- Do you have IPv6 or multiple network interfaces? If so, the server will have an existential crisis when trying to perform these, and you'll need to reassure it by giving it IPs to listen on:
- To do this, include
+ip 0.0.0.0
on the command-line arguments and addrcon_address 0.0.0.0
to your server.cfg - Note:
rcon_address
is not exclusively used by rcon, UDP logging will fail without it.