VPS Tutorials
Set Up a CS2 Dedicated Server on a VPS (2026): SteamCMD, GSLT, Ports, Config, Backups and Auto-Updates
Build a CS2 dedicated server from scratch on an Ubuntu VPS with SteamCMD, covering App ID 730, GSLT, port 27015, systemd, competitive mode, config backups, auto-updates and common troubleshooting.

If you want to practice aim with friends, run 5v5s, host a small tournament or build a long-term community server, running your own Counter-Strike 2 (CS2) dedicated server is usually more controllable than a temporary lobby: you control the maps, modes, password, bans, logs, CSTV and update timing. The real pitfalls aren't downloading the game — they're copying the old CS:GO App ID, binary path and parameters verbatim, forgetting to configure GSLT, opening the wrong port protocol, or failing to back up the config before an update.
This article uses Ubuntu 24.04 LTS, SteamCMD, Valve's official CS2 server files and systemd as an example, and walks through installation, GSLT, port 27015, firewall, competitive mode, server.cfg, auto-updates, config backups and troubleshooting from scratch. The commands in this article target a single Linux VPS, do not use game images of unknown origin, and never write the GSLT or admin password into public files.
The CS2 server does not need a dedicated GPU, but it is sensitive to single-core CPU performance, sustained turbo clocks, network jitter and disk update speed. Valve has not published a set of VPS minimum specs for ordinary community servers that guarantees concurrency, so the following are practical starting points for purchasing and stress testing, not official performance promises:
| Usage scale | Suggested VPS starting point | Disk | Suitable scenario |
|---|---|---|---|
| 5v5 private server | 4 high-performance vCPUs, 8GB RAM | 80GB NVMe | Vanilla competitive, training, friends playing together |
| 12-20 player community server | 6 high-performance vCPUs, 12-16GB RAM | 100GB NVMe | Public server, logs, a few extensions |
| Multi-instance or tournament server | 8+ cores, 16-32GB RAM | 160GB+ NVMe | Multiple ports, CSTV, recordings and tournament operations |
App ID 730 contains the full CS2 game content, and downloads and updates will consume considerable disk and temporary space. Do not buy a small disk based on the size of the old CS:GO standalone server; leave separate space for the system, the server, update headroom, logs and backups.
The data center should be close to the main players. For a shooter, low latency is only the first step; peak-hour packet loss, jitter, CPU overselling and UDP protection matter just as much. Before purchasing, confirm that the provider allows game servers, and understand the traffic, DDoS scrubbing, ban policy after attacks and IP change costs. You can first refer to the game server VPS recommendations and configuration guide.
| Option | Advantages | Cost | Who it suits |
|---|---|---|---|
| SteamCMD + systemd | Closest to Valve's official commands, transparent directory, easy to troubleshoot | Managed mainly via SSH | A single VPS, a single CS2 instance |
| LinuxGSM | Wraps install, update, monitoring and backup commands | Requires understanding LinuxGSM's configuration layer | Maintaining several kinds of Linux game servers at once |
| Pterodactyl | Web panel, users, quotas and multi-instance management | Extra maintenance of Panel, Wings, database and network | Hosting multiple instances or multi-person operations |
This article chooses SteamCMD + systemd so that the App ID, binary path, startup parameters and config files are clearly visible. If you plan to run Minecraft, Rust, Palworld and CS2 at the same time, you can use Set Up a Pterodactyl Game Server Panel on a VPS; to compare another way of managing game servers, see the Rust Dedicated Server LinuxGSM tutorial.
This is the most important difference before deployment:
- Valve's current tournament technical documentation uses
app_update 730 validateto fetch the CS game server. - Linux launches the Source 2
cs2binary and adds-dedicated. - App ID
740belongs to old CS:GO Dedicated Server tutorials and should not be used for current CS2 deployments. - The GSLT used by public servers should also be created under CS AppID
730.
When you see a tutorial still asking you to download 740, run srcds_run or enter the old csgo server directory, first confirm whether it is about CS2 or the superseded CS:GO process.
For a single ordinary instance, deploy with the minimal port set first:
| Port | Protocol | Purpose | Public |
|---|---|---|---|
| 27015 | UDP + TCP | Player connections, queries and server communication | Yes |
| 27020 | UDP | CSTV viewing port | Only open when CSTV is enabled |
| 22 | TCP | SSH management | Admin IP only, or change to the actual SSH port |
Valve's tournament server example has players connect to IP:27015 and CSTV viewers connect to IP:27020. An ordinary private server does not need CSTV, so do not open 27020. Multi-instance deployments should assign each instance its own game port, CSTV port and GSLT; multiple instances must not reuse the same listening port or token.
If you use a domain, for example cs2.example.com, just add an A record pointing to the VPS public IPv4. The ordinary Cloudflare orange-cloud proxy does not forward CS2 game traffic, so use DNS only, or let players connect directly to the public IP.
First use an admin account with sudo privileges to update the system and install the basic dependencies:
sudo apt update
sudo apt upgrade -y
sudo apt install -y curl ca-certificates tar lib32gcc-s1 ufw
Create a cs2 service account that is not used for daily login, and prepare the installation directory:
sudo adduser --disabled-password --gecos "" cs2
sudo install -d -o cs2 -g cs2 /opt/steamcmd
sudo install -d -o cs2 -g cs2 /opt/cs2
sudo -iu cs2
Do not run the game server as root. A dedicated user narrows the privilege scope if the service process is exploited, and also prevents update scripts from writing files as root-owned, which would make SteamCMD unable to overwrite them later.
The following commands are executed as the cs2 user. Download the Linux version of SteamCMD from Valve's Steam CDN:
cd /opt/steamcmd
curl -fsSL https://steamcdn-a.akamaihd.net/client/installer/steamcmd_linux.tar.gz -o steamcmd_linux.tar.gz
tar -xzf steamcmd_linux.tar.gz
./steamcmd.sh +quit
The first run will self-update. Confirm the entry file exists:
test -x /opt/steamcmd/steamcmd.sh
First try an anonymous login to download App ID 730:
/opt/steamcmd/steamcmd.sh \
+force_install_dir /opt/cs2 \
+login anonymous \
+app_update 730 validate \
+quit
force_install_dir should come before login to avoid SteamCMD's path order warning. validate is suitable for the first install or repairing corrupted files; daily updates usually do not need to validate all files every time.
If the current Steam backend requires login, do not write the account password directly into the script. Prepare a separate Steam account that owns CS2 for the server, run SteamCMD interactively, then enter the username, password and Steam Guard verification. Do not use your personal main account, and do not save credentials in shell history or a public repository.
After installation, check the Source 2 Linux binary:
test -x /opt/cs2/game/bin/linuxsteamrt64/cs2
ls -lh /opt/cs2/game/bin/linuxsteamrt64/cs2
du -sh /opt/cs2
A public CS2 server needs a Game Server Login Token. Log in to Steam's game server account management page and create a token with App ID 730.
Steam's official page requires the account to:
- have no community ban or lock;
- not be a limited account;
- have a qualifying phone bound;
- own the corresponding game;
- not distribute the GSLT to third parties.
Each instance uses a different GSLT. If a token leaks, delete it on the Steam page and recreate it immediately; when the Steam password is reset through support channels, existing tokens may also be regenerated. Tokens that have never been logged in for a long time may expire.
Exit the cs2 user and create an environment file readable only by root:
exit
sudo install -d -m 700 -o root -g root /etc/cs2
sudo nano /etc/cs2/cs2.env
Write one line, replacing the example with the real GSLT:
CS2_GSLT=REPLACE_WITH_YOUR_APP_730_GSLT
Restrict permissions:
sudo chown root:root /etc/cs2/cs2.env
sudo chmod 600 /etc/cs2/cs2.env
Do not write the token directly into the systemd unit, server.cfg, screenshots, log tickets or the commands in this article.
CS2's custom server configuration goes in:
/opt/cs2/game/csgo/cfg/server.cfg
Create the file and make sure the service account owns the config directory:
sudo install -d -o cs2 -g cs2 /opt/cs2/game/csgo/cfg
sudo -u cs2 nano /opt/cs2/game/csgo/cfg/server.cfg
Write a conservative starting point for a private competitive server:
hostname "My Private CS2 Server"
sv_password "REPLACE_WITH_PLAYER_PASSWORD"
sv_lan 0
sv_cheats 0
sv_hibernate_when_empty 0
log on
mp_autoteambalance 1
mp_limitteams 1
exec banned_user.cfg
exec banned_ip.cfg
writeid
writeip
Generate a player password:
openssl rand -base64 24
Write the result into sv_password and then restrict file read permissions:
sudo chown cs2:cs2 /opt/cs2/game/csgo/cfg/server.cfg
sudo chmod 600 /opt/cs2/game/csgo/cfg/server.cfg
A public server can set sv_password to an empty string. Do not enable sv_cheats 1 on a production server, and do not directly modify the default game mode files that Valve distributes with updates; custom content should go into server.cfg or the corresponding gamemode_*_server.cfg.
Create the unit:
sudo nano /etc/systemd/system/cs2.service
Write:
[Unit]
Description=Counter-Strike 2 Dedicated Server
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
User=cs2
Group=cs2
WorkingDirectory=/opt/cs2/game/bin/linuxsteamrt64
EnvironmentFile=/etc/cs2/cs2.env
ExecStart=/opt/cs2/game/bin/linuxsteamrt64/cs2 -dedicated -console -usercon -port 27015 +game_type 0 +game_mode 1 +mapgroup mg_active +map de_dust2 +sv_setsteamaccount ${CS2_GSLT} +exec server.cfg
Restart=on-failure
RestartSec=10
KillSignal=SIGINT
TimeoutStopSec=90
LimitNOFILE=1048576
[Install]
WantedBy=multi-user.target
This set of mode parameters represents classic competitive mode: game_type 0, game_mode 1. Valve's current tournament server example also uses these values. To run casual mode, change them to game_type 0, game_mode 0, then restart and check the logs.
Load, start and check the status:
sudo systemctl daemon-reload
sudo systemctl enable --now cs2
sudo systemctl status cs2 --no-pager
sudo journalctl -u cs2 -n 150 --no-pager
The logs should show map loading, Steam game server login and listening port information. If GSLT login fails, do not keep regenerating tokens to mask the problem; check the App ID, whether the token was deleted, the environment file permissions and the Steam account status.
Keep the current SSH session open and allow SSH first:
sudo ufw allow OpenSSH
sudo ufw allow 27015/udp
sudo ufw allow 27015/tcp
sudo ufw status numbered
sudo ufw enable
Add this only when CSTV is enabled:
sudo ufw allow 27020/udp
The cloud provider security group must be configured with the same protocols. Then check the actual listeners:
sudo ss -lntup | grep -E ':27015|:27020'
The most common mistakes are opening only TCP 27015, changing only UFW while missing the cloud security group, or opening a large range of unrelated ports at once. For a single ordinary instance, open only the ports you are sure you need first; if the logs show the instance uses a different port, then investigate the startup parameters and conflicting processes.
Enable the developer console in CS2 settings, press ~ to open the console and enter:
connect 203.0.113.20:27015
Replace the example IP with the VPS's real public IPv4. When using a domain:
connect cs2.example.com:27015
If the server has a password set, the client will prompt for it. If the connection fails, first use the public IP to rule out DNS issues, then check systemd, listening ports, UFW, the cloud security group and the server version.
Enable CSTV only when you actually need spectating, commentary or recording. Add this to the end of the systemd ExecStart:
+tv_enable 1 +tv_port 27020
Reload and restart:
sudo systemctl daemon-reload
sudo systemctl restart cs2
sudo journalctl -u cs2 -n 150 --no-pager
sudo ss -lunp | grep ':27020'
Viewers connect:
connect 203.0.113.20:27020
Valve's official tournament testing process also uses player port 27015 and CSTV port 27020. A private 5v5 that does not need CSTV should keep it off to reduce extra bandwidth, storage and exposure.
The standard maps themselves can be re-downloaded by SteamCMD; what really needs protecting is custom configuration, ban lists, match configs, logs, recordings and plugin data. First create a backup directory:
sudo install -d -m 750 -o root -g cs2 /var/backups/cs2
Stop the server during a maintenance window and archive the config directory:
sudo systemctl stop cs2
sudo tar -C /opt/cs2 -czf \
"/var/backups/cs2/cs2-config-$(date -u +%Y%m%dT%H%M%SZ).tar.gz" \
game/csgo/cfg
sudo systemctl start cs2
sudo systemctl is-active cs2
Check the archive:
sudo ls -lh /var/backups/cs2
sudo tar -tzf /var/backups/cs2/cs2-config-YYYYMMDDTHHMMSSZ.tar.gz | head -n 50
Replace the example filename with the actual generated file. If match recordings, plugins and Workshop content are in other directories, add them to the backup list by their actual paths. Sync at least one copy to another machine or object storage, and do recovery drills regularly. For a complete off-machine backup plan, see the VPS backup and recovery drill guide.
Before restoring, stop the service and keep the current configuration:
sudo systemctl stop cs2
sudo tar -tzf /var/backups/cs2/cs2-config-YYYYMMDDTHHMMSSZ.tar.gz | head -n 50
sudo mv /opt/cs2/game/csgo/cfg \
"/opt/cs2/game/csgo/cfg.before-restore-$(date -u +%Y%m%dT%H%M%SZ)"
sudo tar -C /opt/cs2 -xzf \
/var/backups/cs2/cs2-config-YYYYMMDDTHHMMSSZ.tar.gz
sudo chown -R cs2:cs2 /opt/cs2/game/csgo/cfg
sudo systemctl start cs2
Verify status, config loading and player connections:
sudo systemctl status cs2 --no-pager
sudo journalctl -u cs2 -n 200 --no-pager
sudo ss -lntup | grep ':27015'
Recovery is only complete after you confirm the server name, password, mode, ban list and connections are all normal. Keep cfg.before-restore-* until acceptance, and do not immediately delete the only fallback copy.
When client and server versions differ, players will encounter outdated version or connection failures. Create a maintenance script run by root:
sudo nano /usr/local/sbin/update-cs2-server
Write:
#!/usr/bin/env bash
set -euo pipefail
backup_dir=/var/backups/cs2
stamp=$(date -u +%Y%m%dT%H%M%SZ)
was_active=0
install -d -m 750 -o root -g cs2 "$backup_dir"
if systemctl is-active --quiet cs2; then
was_active=1
systemctl stop cs2
fi
restart_service() {
if [ "$was_active" -eq 1 ]; then
systemctl start cs2
fi
}
trap restart_service EXIT
tar -C /opt/cs2 -czf \
"$backup_dir/cs2-before-update-$stamp.tar.gz" \
game/csgo/cfg
sudo -u cs2 /opt/steamcmd/steamcmd.sh \
+force_install_dir /opt/cs2 \
+login anonymous \
+app_update 730 \
+quit
After saving, set permissions and test manually:
sudo chown root:root /usr/local/sbin/update-cs2-server
sudo chmod 750 /usr/local/sbin/update-cs2-server
sudo /usr/local/sbin/update-cs2-server
sudo systemctl is-active cs2
sudo journalctl -u cs2 -n 150 --no-pager
The script stops the server and backs up the config before updating; even if SteamCMD fails, the exit handler will try to restore the original service state. If your SteamCMD requires interactive login, do not enable unattended updates; run them manually during a maintenance window.
Automatic updates interrupt live matches, so they are only suitable for servers with a fixed low-peak maintenance window and a verified working anonymous SteamCMD. Create the service:
sudo nano /etc/systemd/system/cs2-update.service
[Unit]
Description=Update Counter-Strike 2 Dedicated Server
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/update-cs2-server
Create the timer:
sudo nano /etc/systemd/system/cs2-update.timer
[Unit]
Description=Daily CS2 update maintenance window
[Timer]
OnCalendar=*-*-* 05:30:00
RandomizedDelaySec=10m
Persistent=true
[Install]
WantedBy=timers.target
Load and check the next execution time:
sudo systemctl daemon-reload
sudo systemctl enable --now cs2-update.timer
systemctl list-timers cs2-update.timer
Public community servers are better off monitoring Valve updates first, then notifying players in advance and triggering updates manually. Do not let the timer force a shutdown in the middle of a match.
First record a week of real peak data:
free -h
df -h /opt/cs2 /var/backups/cs2
uptime
ps -C cs2 -o pid,%cpu,%mem,rss,etime,cmd
sudo journalctl -u cs2 --since "30 minutes ago" --no-pager
Focus on:
- Single core constantly saturated, latency spikes during multi-player firefights: migrate to a node with stronger single-core performance and less overselling.
- Memory keeps growing or OOM occurs: first disable unverified plugins, then upgrade memory.
- Updates often fail due to insufficient disk: expand the NVMe and reserve extra space for new version downloads and backups.
- Obvious peak-hour packet loss and jitter: switch to a route closer to players with stable UDP quality.
- CSTV recordings and logs grow quickly: set a separate retention period and do not let the root partition fill up.
Do not blindly believe in "more vCPUs". For a single 5v5 instance, stable single-core performance and network quality are usually more important than a pile of slow cores. Before scaling up, reproduce the problem through real matches and rule out plugins, logs and disk space factors.
Check whether the install command uses App ID 730, and whether the binary exists:
test -x /opt/cs2/game/bin/linuxsteamrt64/cs2
grep -n '"appid"' /opt/cs2/steamapps/appmanifest_730.acf | head
If a tutorial asks for App ID 740 and srcds_run, it is not describing the current CS2 server process.
Check the unit and the full logs:
sudo systemctl status cs2 --no-pager
sudo journalctl -u cs2 -n 250 --no-pager
sudo systemctl cat cs2
Focus on the binary path, run user, working directory, missing 32-bit runtime libraries and the environment file. Do not wrap an unnecessary bash -c in the unit to mask the real exit code.
Confirm the token was created under App ID 730, has not been deleted or expired, and that each instance uses a separate GSLT:
sudo test -s /etc/cs2/cs2.env
sudo stat -c '%a %U:%G %n' /etc/cs2/cs2.env
sudo journalctl -u cs2 -n 250 --no-pager | grep -iE 'steam|token|login|auth'
Do not copy the real token into terminal output, chat or tickets. If you suspect a leak, delete and recreate it directly on the Steam management page.
Check the process, ports, local firewall and cloud security group in order:
sudo systemctl is-active cs2
sudo ss -lntup | grep ':27015'
sudo ufw status numbered
sudo journalctl -u cs2 -n 200 --no-pager
Use a direct public IP connection to rule out DNS issues. Confirm both UDP and TCP rules exist, and verify that the actual systemd startup parameters have not changed to another port.
First notify players and run a maintenance update:
sudo /usr/local/sbin/update-cs2-server
sudo systemctl is-active cs2
sudo journalctl -u cs2 -n 200 --no-pager
If it still fails, validate with app_update 730 validate; do not delete the entire installation directory, and certainly do not delete the config backups.
Confirm the file is located at:
/opt/cs2/game/csgo/cfg/server.cfg
And check that the startup command explicitly includes +exec server.cfg. If a parameter is also defined in the systemd ExecStart, the command-line value may override the config file. After modifying the unit, run systemctl daemon-reload; after modifying the config, at least restart the service and review the logs.
- SteamCMD uses App ID 730, and the server binary is
linuxsteamrt64/cs2. - The service runs as a dedicated
cs2user, not root. - The GSLT is created under CS AppID 730, separate per instance and not public.
-
/etc/cs2/cs2.envhas permissions 600 and is owned by root. -
systemctl is-active cs2returnsactive. - 27015 TCP/UDP is correctly allowed in both UFW and the cloud security group.
- 27020 is not open when CSTV is unused; after enabling it, viewer connections have been verified.
- The Steam client can join via the public IP or a DNS-only domain.
-
server.cfgtakes effect, andsv_cheatsstays off. - Configs are backed up before updates, with at least one copy kept off-machine.
- A config restore has actually been performed, and the server name, mode and ban list verified.
- Scheduled updates run only in a clear maintenance window and do not interrupt matches.
Running a CS2 dedicated server stably on a VPS is not about copying a single startup command; it is about using the current App ID 730 and the Source 2 cs2 binary, securely storing a separate GSLT per instance, correctly opening 27015 TCP/UDP, and getting config backups, systemd supervision and update maintenance windows right together.
A private 5v5 can start with 4 high-performance vCPUs, 8GB RAM and 80GB NVMe, while a public server should scale up gradually based on actual CPU peaks, network jitter, logs and CSTV space. Doing a real connection, version update and recovery drill before going live reduces match interruptions more than blindly adding ports, plugins or automation.