Установка игрового сервера через SteamCMD
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 клиента.
Как понять, что всё действительно работает
Четыре проверки, именно в таком порядке:
- Файлы на месте.
du -sh /home/steam/valheimдолжна показать правдоподобный размер, а вsteamapps/appmanifest_896660.acfстоитbuildid. Каталог в несколько мегабайт означает прерванную загрузку. - Процесс живёт дольше минуты.
systemctl status valheimпоказываетactive (running). Сервис, который перезапускается каждую секунду, стоит вactivating (auto-restart), тогда причину выдастjournalctl -u valheim -n 50. - Порт открыт, причём именно как UDP.
ss -ulpn | grep 2456должна назвать процесс. Если ничего не видно, значит сервер ещё инициализируется или привязан не к тому адресу. - Доступен снаружи. Только после этого проверяйте файрвол и подключайтесь из самой игры. Многим тайтлам дополнительно нужен query-порт, у Valheim это порт сервера плюс один.
Если эти четыре пункта сходятся, фундамент в порядке, и всё остальное — уже настройка самой игры. На root-сервере KernelHost в дата-центре maincubes во Франкфурте-на-Майне к этому добавляется защита от DDoS из собственной сети, что особенно важно как раз для публично перечисленных игровых серверов, ведь публичный список серверов делает IP-адрес видимым для каждого. Подробности вы найдёте в статье Защита сервера от DDoS-атак.
Частые вопросы
Почему SteamCMD не запускается на моём 64-битном сервере?
Нужна ли учётная запись Steam для игрового сервера?
Что означает «App state is 0x606 after update job»?
Нужно ли указывать validate при каждом обновлении?
Как обновляться автоматически, не перезапуская сервер каждый день?
Почему apt не находит пакет steamcmd?
2026 KernelHost GmbH. Все права защищены. Эта инструкция охраняется авторским правом. Публикация на других сайтах, в том числе частично или в изменённом виде, без нашего письменного согласия не разрешается. Цитирование с указанием источника и активной ссылкой мы приветствуем.

