Установка игрового сервера через SteamCMD

Опубликовано 14 мин. чтения

SteamCMD — общая основа почти всех игровых серверов в Steam. Руководство показывает установку, анонимный вход, app_update с validate, работу в виде systemd-сервиса и типичные картины ошибок.

Кто держит игровой сервер на собственном root-сервере, почти всегда приходит к одному и тому же инструменту: SteamCMD. Valheim, Rust, Counter-Strike 2, Palworld, Enshrouded или ARK: файлы сервера лежат у Valve и загружаются через один и тот же клиент командной строки. Кто один раз аккуратно настроил SteamCMD, ставит каждую следующую игру, поменяв одно число.

Это руководство закрывает ровно те места, на которых короткие инструкции обрываются: 32-битные библиотеки на 64-битных системах, различия между Debian, Ubuntu и семейством Red Hat, дословные тексты сообщений об ошибках, схема обновления, которая не перезапускает сервер при каждом запуске cron, и работа в виде systemd-сервиса.

Что такое SteamCMD и чем он не является

SteamCMD — это Steam Console Client, урезанная консольная версия клиента Steam без графической оболочки. Хорошо он умеет ровно одно: скачивать приложения из сети Steam, обновлять их и проверять их файлы. Он не запускает сервер, ничего не настраивает и об игровой логике ничего не знает. После загрузки файлы сервера оказываются в каталоге, а дальше в дело вступает сама игра.

Важно для понимания всей истории с библиотеками: загрузчик SteamCMD по сей день остаётся 32-битной программой. На чистой 64-битной системе ему не хватает подходящих библиотек времени выполнения, и именно на этом у большинства спотыкается первый запуск. Сами скачанные файлы сервера у современных игр почти всегда 64-битные.

Подготовка: отдельный пользователь вместо root

Процессы игрового сервера никогда не работают от root. Они принимают соединения из интернета, у многих тайтлов подгружают моды из Steam Workshop и выполняют чужой код. Отдельный непривилегированный пользователь стоит трёх команд и ограничивает возможный ущерб.

useradd -m -d /home/steam -s /bin/bash steam
mkdir -p /home/steam/steamcmd
chown -R steam:steam /home/steam/steamcmd

При запуске от root SteamCMD прямо предупреждает об этом. Кто предупреждение проигнорирует, получит проблему обратно позже: как только каталог однажды достался root, последующий запуск от steam завершается ошибкой записи, а текст сообщения на причину не указывает.

Все дальнейшие команды в этой статье вводятся от root и переключаются в служебного пользователя через runuser -u steam --. На системах с sudo точно так же работает sudo -u steam. Кто перед этим хочет закрыть базовую защиту, найдёт основы в статьях Настройка нового root-сервера и Защита SSH.

32-битные библиотеки, в каждой системе свои

Здесь дистрибутивы расходятся, и универсальные списки команд из интернета регулярно оказываются неверными.

Debian 12, Debian 13, Ubuntu 22.04 и Ubuntu 24.04

На всех четырёх системах достаточно пакета lib32gcc-s1 из основного архива. Это пакет для amd64, который приносит с собой 32-битную среду выполнения, и никакая дополнительная архитектура i386 ему не нужна:

apt update
apt install -y ca-certificates curl tar file lib32gcc-s1 lib32stdc++6

lib32stdc++6 самому SteamCMD нужен не всегда, но его требуют довольно многие старые серверные бинарники (всё на движке Source, тайтлы на HLDS, часть серверов на Unity). Поставить его сразу дешевле, чем потом искать причину ошибки. file не входит ни в один минимальный образ, а ниже он понадобится для диагностики, поэтому он сразу стоит в списке.

AlmaLinux 9, Rocky Linux 9, RHEL 9 и Oracle Linux 9

Там пакеты называются иначе и несут суффикс .i686:

dnf -y update
dnf install -y --allowerasing glibc.i686 libstdc++.i686 tar file curl

Оба дополнения нужны на свежей минимальной установке, иначе команда прервётся. Причина для --allowerasing: EL9 приносит с собой curl-minimal, и полный curl с ним конфликтует. Без этого ключа вызов на AlmaLinux 9 и Rocky Linux 9 заканчивается сообщением package curl-minimal ... conflicts with curl provided by curl ... conflicting requests. Кто вычеркнет curl из списка совсем, тоже придёт к цели, ведь curl-minimal уже поставляет /usr/bin/curl. Причина для предварительного dnf -y update: если состояние системы старше репозиториев, свежий пакет i686 конфликтует с установленным 64-битным пакетом, на Rocky Linux 9 это выглядело как file /usr/share/gcc-11/python/libstdcxx/v6/printers.py from install of libstdc++-11.5.0-14.el9.i686 conflicts with file from package libstdc++-11.4.1-2.1.el9.x86_64. На Oracle Linux 9 ни один из двух конфликтов не возникает, там полный curl уже установлен.

EPEL не нужен, пакеты лежат в стандартных репозиториях. Кому дополнительно нужны инструменты вроде htop, найдёт путь туда в статье Установка htop на AlmaLinux, Rocky и RHEL.

AlmaLinux 10, Rocky Linux 10 и RHEL 10 для SteamCMD не подходят

Это не проблема конфигурации, а тупик: RHEL 10 и производные от него дистрибутивы полностью убрали 32-битную архитектуру x86. Пакетов i686 там нет ни в одном репозитории, в том числе и с --enablerepo=*, команда установки заканчивается сообщением No match for argument: glibc.i686. Поскольку steamcmd.sh всегда вызывает 32-битный загрузчик linux32/steamcmd, обойти это невозможно. Для игрового сервера на стороне Red Hat поэтому нужно выбирать семейство EL9, то есть AlmaLinux 9, Rocky Linux 9 или Oracle Linux 9.

Второй камень преткновения на очень скромных установках EL: если стоит только util-linux-core, не хватает runuser, который используется в этом руководстве повсюду. Тогда сначала выполните dnf install -y util-linux. На AlmaLinux 9, Rocky Linux 9, Oracle Linux 9, а также на всех системах Debian и Ubuntu runuser уже есть.

Почему не взять просто пакет steamcmd?

Ubuntu держит steamcmd в компоненте multiverse, Debian в non-free, и в обоих случаях пакет собран исключительно для архитектуры i386. Поэтому на свежеустановленном 64-битном сервере вы стабильно упираетесь в:

E: Unable to locate package steamcmd

Чтобы он вообще стал кандидатом, сначала нужно подключить компонент и включить чужую архитектуру. В Ubuntu multiverse активен уже в стандартной установке, там хватает трёх строк:

dpkg --add-architecture i386
apt update
apt-cache policy steamcmd

В Debian не хватает ещё и компонента. Без него apt-cache policy steamcmd вообще не выдаёт никакого вывода, то есть даже сообщения об ошибке. Сначала contrib и non-free должны попасть в источники пакетов, начиная с Debian 12 обычно в /etc/apt/sources.list.d/debian.sources в строку Components:. Только после этого запрос срабатывает и сообщает на Debian 12 о кандидате 0~20180105-5 из bookworm/non-free i386. Команда apt update между переключением архитектуры и запросом обязательна: без неё списки пакетов i386 не существуют и кандидат остаётся (none).

Затраты окупаются редко. Пакет Debian — это очень старая обёртка (версия 0~20180105-5 в Debian 13), которая всё равно скачивает тот же самый загрузчик, что вы двумя строками получаете сами. Ручная установка одинакова на всех системах и потому лучше документируется.

Установка SteamCMD и чтение первого запуска

runuser -u steam -- curl -sSLo /home/steam/steamcmd/steamcmd_linux.tar.gz https://media.steampowered.com/client/installer/steamcmd_linux.tar.gz
runuser -u steam -- tar -xzf /home/steam/steamcmd/steamcmd_linux.tar.gz -C /home/steam/steamcmd
runuser -u steam -- /home/steam/steamcmd/steamcmd.sh +quit

Третья команда и есть собственно тест. При самом первом вызове SteamCMD догружает сам себя, показывает индикатор прогресса и завершается. Выглядит это так:

[  0%] Checking for available update...
[----] Downloading update (0 of 58,393 KB)...
[100%] Download complete.
[----] Extracting package...
[----] Installing update...
[----] Verifying installation...
Steam Console Client (c) Valve Corporation - version 1751...
Loading Steam API...OK

Критерий успеха: последняя строка гласит Loading Steam API...OK. Если вместо этого появляется Loading Steam API...FAILED или процесс сразу обрывается, значит не хватает 32-битных библиотек из предыдущего раздела. Типичная формулировка при этом такая:

steamcmd.sh: line 41: /home/steam/steamcmd/linux32/steamcmd: No such file or directory

Сообщение вводит в заблуждение, потому что файл как раз существует. Не найден 32-битный интерпретатор, который нужен этому бинарнику. Проверить это можно командой file /home/steam/steamcmd/linux32/steamcmd, вывод гласит ELF 32-bit LSB shared object, Intel 80386, ... interpreter /lib/ld-linux.so.2 и тем самым прямо называет недостающий интерпретатор. Если сам file отвечает command not found, значит его забыли на шаге установки выше. Вариант error while loading shared libraries: libstdc++.so.6 показывает ту же проблему, только уровнем позже.

Анонимный вход против входа под учётной записью

Подавляющее большинство выделенных игровых серверов опубликованы как отдельные бесплатные приложения Steam и загружаются без учётных данных:

+login anonymous

Это обычный случай и тот путь, который всегда стоит пробовать первым. Никакого пароля на сервере, никакого Steam Guard, никаких заблокированных учётных записей после переезда сервера.

Вход под учётной записью нужен только тогда, когда серверная сборка привязана к владению игрой. Узнаётся это по такому сообщению:

ERROR! Failed to install app 'ID' (No subscription)

No subscription всегда означает: этой учётной записи нельзя загрузить это приложение. При анонимном входе это значит, что нужна настоящая учётная запись с купленной игрой. При настоящей учётной записи это значит, что лицензии нет или использован неверный App-ID.

Вход под учётной записью проходит интерактивно, потому что Steam Guard требует код:

/home/steam/steamcmd/steamcmd.sh +login ваш_логин

После однократного подтверждения в ~/.steam появляется sentry-файл, после чего работают и неинтерактивные вызовы. Три момента, которые на практике болезненны: заведите для этой цели отдельную учётную запись Steam, которая держит только лицензию для сервера. Никогда не записывайте пароль в cron-скрипт. И рассчитывайте на то, что после долгого простоя или смены IP Steam снова попросит подтверждение, из-за чего автоматическое обновление молча зависнет.

Отдельно от этого стоит рассматривать Game Server Login Token (GSLT). К загрузке он отношения не имеет, он решает, появится ли работающий сервер в публичном списке серверов. У Counter-Strike 2 и других тайтлов Valve он создаётся в учётной записи Steam и вписывается в конфигурацию сервера, а не в SteamCMD.

Установка игры: порядок аргументов, validate и ветки

SteamCMD обрабатывает аргументы с + строго слева направо. Отсюда следует самое важное правило:

+force_install_dir должен стоять перед +app_update. Если он стоит после, файлы попадают в стандартный путь, а указанный каталог остаётся пустым.

Полный вызов, здесь на примере сервера Valheim с App-ID 896660:

runuser -u steam -- mkdir -p /home/steam/valheim
runuser -u steam -- /home/steam/steamcmd/steamcmd.sh +force_install_dir /home/steam/valheim +login anonymous +app_update 896660 validate +quit

Обратите внимание, что validate пишется без плюса: это аргумент команды app_update, а не отдельная команда. validate сравнивает каждый файл с контрольной суммой из депо и заново загружает расхождения. Осмысленно это при первой установке, после прерванной загрузки и при странных падениях. При каждом рутинном обновлении это лишнее и дорого, потому что читается весь массив данных. Два побочных эффекта знать стоит: самостоятельно изменённые файлы, относящиеся к депо, откатываются назад, а у игр с модами в каталоге установки validate может удалить чужие файлы.

Контролем успеха служит заключительная строка:

Success! App '896660' fully installed.

Другая ветка, например публичная тестовая, дописывается прямо к app_update:

+app_update 896660 -beta public-test validate

Для ветки, защищённой паролем, добавляется -betapassword. Обратный путь на стандартную версию — это -beta none, простого пропуска параметра недостаточно, потому что выбор ветки сохраняется в манифесте.

Если что-то пошло не так: сообщения об ошибках дословно

SteamCMD сообщает о проблемах шестнадцатеричным статусом, который без перевода не говорит ничего. Самые частые:

  • Error! App '...' state is 0x202 after update job: не хватает места на накопителе. Проверьте командой df -h /home/steam. Помните, что кроме целевого каталога SteamCMD нужно место под промежуточное хранилище загрузки, как правило в той же файловой системе в steamapps/downloading. У крупных тайтлов пиковая потребность во время обновления может быть почти вдвое больше итогового размера. Если накопитель заполнен, поможет статья Диск заполнен: наводим порядок в Linux.
  • Error! App '...' state is 0x606 after update job: ошибка записи. В девяти случаях из десяти дело в правах, а не в оборудовании. Классика: первый запуск прошёл от root, второй от steam. Чинится командой chown -R steam:steam /home/steam. При символических ссылках на второй накопитель цель тоже должна принадлежать служебному пользователю.
  • Error! App '...' state is 0x402 after update job: нет пригодного соединения с контент-серверами Steam. Чаще всего это исходящее правило файрвола или проблема с DNS. SteamCMD нужны исходящие TCP 443, а также с 27015 по 27050.
  • No subscription: тема лицензии, смотрите предыдущий раздел.
  • Failed to load steamclient.so или [S_API FAIL] SteamAPI_Init(): серверный бинарник ищет библиотеку Steam в жёстко заданном месте, которое SteamCMD не заполняет. Затронуты практически все серверы на Unreal Engine, а также Rust, Palworld и V Rising. Решение — это две ссылки в домашнем каталоге служебного пользователя.
runuser -u steam -- mkdir -p /home/steam/.steam/sdk64
runuser -u steam -- ln -sf /home/steam/steamcmd/linux64/steamclient.so /home/steam/.steam/sdk64/steamclient.so
runuser -u steam -- mkdir -p /home/steam/.steam/sdk32
runuser -u steam -- ln -sf /home/steam/steamcmd/linux32/steamclient.so /home/steam/.steam/sdk32/steamclient.so

Так как это ссылки на каталог SteamCMD, после самообновления SteamCMD они остаются рабочими. Частая производная ошибка: создание этих каталогов от root, из-за чего сервис потом не может их прочитать.

Если загрузка застревает надолго, последний рычаг — это удаление файла steamapps/appmanifest_<ID>.acf в каталоге установки. После этого SteamCMD считает приложение неустановленным и загружает его полностью заново.

Автоматизация обновлений без бессмысленных перезапусков сервера

Распространённый совет звучит так: раз в сутки запускать по cron app_update и перезапускать сервис. Это стоит одного перерыва каждый день, даже когда обновления вообще нет. Лучше сравнивать build-ID. Установленный записан в файле манифеста, актуальный выдаёт app_info_print:

runuser -u steam -- /home/steam/steamcmd/steamcmd.sh +login anonymous +app_info_update 1 +app_info_print 896660 +quit

Из этого получается скрипт в /usr/local/sbin/steam-update.sh:

#!/bin/bash
set -euo pipefail
APPID=896660
DIR=/home/steam/valheim
CMD=/home/steam/steamcmd/steamcmd.sh
UNIT=valheim

MANIFEST="$DIR/steamapps/appmanifest_${APPID}.acf"
installed=$(awk '/"buildid"/ {gsub(/"/,"",$2); print $2; exit}' "$MANIFEST" 2>/dev/null || echo 0)
latest=$(runuser -u steam -- "$CMD" +login anonymous +app_info_update 1 +app_info_print "$APPID" +quit \
  | awk '/"public"/{f=1} f && /"buildid"/ {gsub(/"/,"",$2); print $2; exit}')

if [ -z "$latest" ]; then
  echo "Не удалось определить актуальный build-ID, прерываю."
  exit 1
fi

if [ "$installed" = "$latest" ]; then
  echo "Всё актуально (build $installed), перезапуск не нужен."
  exit 0
fi

echo "Обновление с $installed до $latest"
systemctl stop "$UNIT"
runuser -u steam -- "$CMD" +force_install_dir "$DIR" +login anonymous +app_update "$APPID" +quit
systemctl start "$UNIT"

Скрипт перезапускает сервер только тогда, когда действительно что-то изменилось, и корректно прерывается, если Steam в этот момент не отвечает. Без этой проверки пустой результат запроса был бы истолкован как обновление, и сервер остановили бы без причины. Запускается он по cron или, что лучше, через systemd-таймер, основы описаны в статье Настройка cron-задания в Linux.

Учитывайте, что многие игры после обновления сервера требуют и обновлённых клиентов. Автоматическое обновление посреди основного игрового времени выкидывает всех игроков. Поставьте запуск на ранние утренние часы.

Работа в виде systemd-сервиса

Игровой сервер в сессии screen не переживает перезагрузку. Юнит в /etc/systemd/system/valheim.service решает это:

[Unit]
Description=Valheim Dedicated Server
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=steam
Group=steam
WorkingDirectory=/home/steam/valheim
Environment=LD_LIBRARY_PATH=/home/steam/valheim/linux64
Environment=SteamAppId=892970
ExecStart=/home/steam/valheim/valheim_server.x86_64 -nographics -batchmode -name "KernelHost" -port 2456 -world "Dedicated" -password "ваш_пароль"
Restart=on-failure
RestartSec=10
KillSignal=SIGINT
TimeoutStopSec=90
LimitNOFILE=65535

[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable --now valheim
systemctl status valheim

Две строки здесь важнее, чем выглядят. KillSignal=SIGINT заботится о том, чтобы сервер при остановке сохранился, ведь немало игровых серверов игнорируют SIGTERM и при жёстком завершении теряют прогресс последних минут. TimeoutStopSec даёт ему на это время. Подробное объяснение всех полей вы найдёте в статье Создание systemd-сервиса, порядок действий для сервера на Java в статье Автозапуск сервера Minecraft.

Если сервер сразу умирает при старте из-за нехватки оперативной памяти, поможет взгляд на статью Настройка swap. Порты после этого нужно внести в файрвол, смотрите Настройка UFW, причём игровым серверам почти всегда нужен UDP, и запись только для TCP как раз и есть типичная причина того, что сервер работает, но подключиться никто не может.

App-ID распространённых игр

ID — это единственное место, которое меняется от игры к игре. Анонимно загружаются, среди прочего:

  • 232250 Team Fortress 2
  • 4020 Garry's Mod
  • 222860 Left 4 Dead 2
  • 730 Counter-Strike 2 (клиент и сервер после перехода с CS:GO стали одним приложением, соответственно велик и объём загрузки)
  • 258550 Rust
  • 376030 ARK: Survival Evolved
  • 896660 Valheim
  • 2394010 Palworld
  • 2278520 Enshrouded
  • 1829350 V Rising
  • 294420 7 Days to Die
  • 380870 Project Zomboid
  • 1690800 Satisfactory
  • 581330 Insurgency: Sandstorm
  • 233780 Arma 3
  • 1007 Steamworks SDK Redistributables, крошечный и потому идеальный для проверки работоспособности установки

Не путайте серверное приложение с игровым. У ARK, например, 346110 — это игра, а 376030 — сервер. Кто ищет ID, найдёт его в Dedicated Servers List в Valve Developer Wiki или через SteamDB. Быстрая проверка всегда одна и та же: анонимный app_update, и если приходит No subscription, значит это был ID клиента.

Как понять, что всё действительно работает

Четыре проверки, именно в таком порядке:

  1. Файлы на месте. du -sh /home/steam/valheim должна показать правдоподобный размер, а в steamapps/appmanifest_896660.acf стоит buildid. Каталог в несколько мегабайт означает прерванную загрузку.
  2. Процесс живёт дольше минуты. systemctl status valheim показывает active (running). Сервис, который перезапускается каждую секунду, стоит в activating (auto-restart), тогда причину выдаст journalctl -u valheim -n 50.
  3. Порт открыт, причём именно как UDP. ss -ulpn | grep 2456 должна назвать процесс. Если ничего не видно, значит сервер ещё инициализируется или привязан не к тому адресу.
  4. Доступен снаружи. Только после этого проверяйте файрвол и подключайтесь из самой игры. Многим тайтлам дополнительно нужен query-порт, у Valheim это порт сервера плюс один.

Если эти четыре пункта сходятся, фундамент в порядке, и всё остальное — уже настройка самой игры. На root-сервере KernelHost в дата-центре maincubes во Франкфурте-на-Майне к этому добавляется защита от DDoS из собственной сети, что особенно важно как раз для публично перечисленных игровых серверов, ведь публичный список серверов делает IP-адрес видимым для каждого. Подробности вы найдёте в статье Защита сервера от DDoS-атак.

Частые вопросы

Почему SteamCMD не запускается на моём 64-битном сервере?
Загрузчик SteamCMD — это 32-битная программа. Без подходящей среды выполнения он сообщает примерно так: «linux32/steamcmd: No such file or directory», хотя файл существует. На Debian и Ubuntu это лечится командой «apt install lib32gcc-s1 lib32stdc++6», на AlmaLinux 9, Rocky Linux 9 и Oracle Linux 9 сначала «dnf -y update», затем «dnf install --allowerasing glibc.i686 libstdc++.i686». Дополнительная архитектура i386 на Debian и Ubuntu не нужна. В семействе EL10 (AlmaLinux 10 и родственные) пакетов i686 нет вообще, там SteamCMD работать не может.
Нужна ли учётная запись Steam для игрового сервера?
В большинстве случаев нет. Выделенные игровые серверы опубликованы как отдельные бесплатные приложения Steam и загружаются через «+login anonymous». Настоящая учётная запись нужна только тогда, когда SteamCMD при загрузке сообщает «No subscription». От этого следует отличать Game Server Login Token: к загрузке он отношения не имеет, а решает, появится ли работающий сервер в публичном списке серверов.
Что означает «App state is 0x606 after update job»?
Это ошибка записи, которая почти всегда идёт от неверных прав на файлы, а не от дефекта накопителя. Типичная причина: первый запуск от root, второй от служебного пользователя. Команда «chown -R steam:steam /home/steam» наводит порядок. Родственный статус 0x202, наоборот, означает нехватку свободного места, там помогает только уборка или больше места.
Нужно ли указывать validate при каждом обновлении?
Нет. При первой установке, после прерванной загрузки и при необъяснимых падениях validate уместен. В рутинной работе он при каждом запуске читает весь массив данных и напрасно расходует время и I/O. Кроме того, он откатывает самостоятельно изменённые файлы и у некоторых игр может удалить моды из каталога установки.
Как обновляться автоматически, не перезапуская сервер каждый день?
Сравнивайте build-ID. Установленный записан в steamapps/appmanifest_<ID>.acf, актуальный выдаёт «+app_info_update 1 +app_info_print <ID>». Только когда оба значения расходятся, сервис останавливается, обновляется и запускается снова. Важно предусмотреть выход, если запрос не дал результата, иначе при сбое на стороне Steam скрипт остановит сервер без причины.
Почему apt не находит пакет steamcmd?
Потому что в Ubuntu он лежит в multiverse, в Debian в non-free и собран исключительно для архитектуры i386. Без подключённого компонента и без «dpkg --add-architecture i386» появляется «E: Unable to locate package steamcmd». Поскольку пакет всё равно догружает тот же самый загрузчик, ручная установка из tar-архива проще и одинакова на всех дистрибутивах.

SteamCMD Игровой сервер Linux Debian Ubuntu systemd Valheim Counter-Strike 2 root-сервер