Discord-бот на VPS: запуск через systemd с автоперезапуском
Как поставить Python-бота на VPS, вынести токен в файл окружения и настроить systemd, чтобы бот поднимался после перезагрузки и сам перезапускался после падения.
Содержание 18
- Коротко
- Что понадобится
- Шаги
- Примеры
- Slash-команды вместо !-команд
- Как посмотреть бота в кабинете EGN
- Симптом → причина → решение
- Пример: ограничение прав через роли
- Пример: команда с ответом
- Частые ошибки
- Итог
- Чек-лист после запуска
- Как обновить код без простоя
- Вопросы и ответы
- Нужен ли мощный VPS для Discord-бота?
- Можно ли запускать бота на Node.js?
- Зачем отдельный пользователь botuser?
- Что делать, если токен утёк?
Бот на домашнем компьютере работает, пока компьютер включён. Как только вы его выключили, сообщество остаётся без бота. VPS решает эту проблему: машина работает круглосуточно, а systemd поднимает бота после перезагрузки и перезапускает его, если процесс упал. Ниже — полная схема для Python-бота на Ubuntu 24.04.
Коротко
Поставьте discord.py в виртуальное окружение, создайте в Developer Portal бота и включите в нём Message Content Intent. Токен положите в файл .env с правами 600, а службу опишите в /etc/systemd/system/discord-bot.service с Restart=always. Команда systemctl enable --now discord-bot запускает бота и включает автозапуск.
Что понадобится
Нужен VPS с Ubuntu 24.04 или Debian 12, доступ по SSH по ключу (ключи SSH настраиваются до установки бота) и аккаунт Discord с приложением, созданным в Discord Developer Portal. Бот подключается к Discord сам, через исходящее WebSocket-соединение, поэтому входящий порт открывать не нужно.
Шаги
- Создайте приложение в Developer Portal, на вкладке Bot нажмите «Reset Token» и скопируйте токен. Токен — это пароль бота: его нельзя публиковать и вставлять в код.
- На той же вкладке Bot в разделе «Privileged Gateway Intents» включите Message Content Intent. Без него бот в текстовых каналах не увидит содержимое сообщений.
- Установите Python-окружение и библиотеку:
sudo apt update && sudo apt install -y python3-venv
sudo useradd -r -s /usr/sbin/nologin botuser
sudo mkdir -p /opt/discord-bot && sudo chown botuser:botuser /opt/discord-bot
cd /opt/discord-bot
python3 -m venv venv
./venv/bin/pip install -U discord.py- Напишите
bot.py. Токен читается из переменной окружения, в коде его нет:
import os
import discord
intents = discord.Intents.default()
intents.message_content = True
client = discord.Client(intents=intents)
@client.event
async def on_ready():
print(f"Бот подключён как {client.user}")
@client.event
async def on_message(message):
if message.author == client.user:
return
if message.content == "!ping":
await message.channel.send("pong")
client.run(os.environ["DISCORD_TOKEN"])- Создайте файл окружения
/opt/discord-bot/.env(например, черезsudo nano) с одной строкой:
DISCORD_TOKEN=вставьте-токен-сюдаЗатем закройте его от чужих глаз. Токен без кавычек и пробелов вокруг =:
sudo chown botuser:botuser /opt/discord-bot/.env
sudo chmod 600 /opt/discord-bot/.envФайл bot.py тоже должен принадлежать botuser или быть читаемым им: sudo chown botuser:botuser /opt/discord-bot/bot.py.
- Опишите службу systemd в файле
/etc/systemd/system/discord-bot.service:
[Unit]
Description=Discord bot
After=network-online.target
Wants=network-online.target
[Service]
User=botuser
WorkingDirectory=/opt/discord-bot
EnvironmentFile=/opt/discord-bot/.env
ExecStart=/opt/discord-bot/venv/bin/python bot.py
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target- Примените настройки и запустите бота:
sudo systemctl daemon-reload
sudo systemctl enable --now discord-bot
sudo systemctl status discord-bot- Проверьте журнал. Логи бота попадают в
journalctl, его можно смотреть в реальном времени:
sudo journalctl -u discord-bot -f- Пригласите бота на сервер через OAuth2 URL Generator в Developer Portal. Выберите scope
botи нужные права, например «Send Messages» и «Read Message History».
Примеры
Перезапуск после падения. Имитация: ошибка в коде, процесс завершился с ненулевым кодом. Благодаря Restart=always systemd через RestartSec=5 запускает бота заново. В журнале появятся две записи: остановка и новый старт.
Перезагрузка VPS. Команда sudo reboot. После загрузки служба стартует сама, потому что enable записал её в автозапуск. Проверьте командой systemctl is-enabled discord-bot, она должна вывести enabled.
Обновление кода. Положите новый bot.py в каталог, затем sudo systemctl restart discord-bot. Токен и окружение при этом не меняются.
Вынос токена. Чтобы сменить токен, отредактируйте .env и перезапустите службу. Если токен утёк, сбросьте его в Developer Portal, иначе старый продолжит работать у злоумышленника.
Slash-команды вместо !-команд
Discord развивает slash-команды, и для них не нужен Message Content Intent. В discord.py 2.x они описываются через app_commands:
from discord import app_commands
tree = app_commands.CommandTree(client)
@tree.command(name="ping", description="Проверка связи")
async def ping(interaction: discord.Interaction):
await interaction.response.send_message("pong")
@client.event
async def on_ready():
await tree.sync()Команду нужно синхронизировать с Discord вызовом tree.sync(). Делайте это не при каждом запуске, если бот часто перезапускается: у синхронизации есть лимиты по частоте. Надёжнее вынести её в отдельную команду владельца или запускать один раз после изменения списка команд. Бота при этом нужно приглашать со scope applications.commands вместе с bot.
Как посмотреть бота в кабинете EGN
Если бот работает на VPS EGN, к нему можно попасть по SSH и через «Консоль» в карточке сервера: она открывает терминал в браузере. Консоль полезна, когда ключ SSH остался на другом компьютере или закрыт порт 22. Откройте карточку сервера, нажмите «Консоль» и выполните те же команды, что и в SSH-клиенте.
После входа полезны три команды:
sudo systemctl status discord-bot --no-pager
sudo journalctl -u discord-bot -n 100 --no-pager
sudo systemctl restart discord-botПервая показывает, работает ли служба и когда она стартовала. Вторая выводит последние сто строк журнала. Третья перезапускает бота после правки кода или .env.
Симптом → причина → решение
| Симптом | Частая причина | Что сделать |
|---|---|---|
LoginFailure: Improper token |
Токен скопирован с ошибкой или сброшен | Сбросьте токен в Developer Portal и обновите .env |
| Бот онлайн, но команды не работают | Нет Message Content Intent или нет прав в канале | Включите intent и проверьте роль бота в настройках канала |
ModuleNotFoundError: No module named 'discord' |
Служба запускает не тот Python | В ExecStart укажите /opt/discord-bot/venv/bin/python |
| Служба перезапускается каждые 5 секунд | Ошибка в коде на старте | Смотрите journalctl, исправьте исключение, перезапустите |
| Бот не отвечает на slash-команды | Команды не синхронизированы с Discord | Вызовите await tree.sync() (см. раздел про slash-команды ниже) |
После reboot бота нет |
Нет enable |
Выполните sudo systemctl enable discord-bot |
Пример: ограничение прав через роли
Не давайте боту права администратора без необходимости. Если боту нужно только писать в канал, выдайте роли право «Отправка сообщений» только в нужном канале. Права выдаются в настройках сервера Discord, а не в коде. Бот с правами администратора может удалить каналы и роли, поэтому это первое, что стоит проверить после приглашения.
Пример: команда с ответом
Добавьте в bot.py команду, которая отвечает текущим временем сервера. Это удобно для проверки часового пояса на VPS. Новый обработчик заменяет прежний on_message: в файле он должен быть один, поэтому объедините условия в одной функции:
import datetime
@client.event
async def on_message(message):
if message.author == client.user:
return
if message.content == "!time":
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
await message.channel.send(f"Время на сервере: {now}")Если ответ отличается от местного времени на несколько часов, проверьте часовой пояс VPS командой timedatectl.
Частые ошибки
- Бот в сети, но не отвечает на
!ping. Чаще всего не включён Message Content Intent. Включите его в Developer Portal и перезапустите службу. PrivilegedIntentsRequired. Discord не разрешает привилегированный intent, потому что он не включён в портале. Исправляется тем же шагом.- Токен в коде или в git. Так токен утекает. Держите его в
.envс правами 600 и в.gitignore. - Служба падает сразу после старта. Проверьте путь в
ExecStart: должен быть путь к Python внутри venv, а не системныйpython3. Журнал покажет ошибку импорта. - Бот не поднимается после перезагрузки. Нет
enable, или в[Install]не указанWantedBy=multi-user.target. - Служба перезапускается по кругу. Если бот падает при каждом старте,
Restart=alwaysбудет поднимать его снова и снова с паузойRestartSec=5. Это не лечит причину: найдите исключение в журнале. Пока бот падает, службу можно остановить:sudo systemctl stop discord-bot.
Итог
Бот на VPS работает круглосуточно, если у него есть три вещи: окружение Python с discord.py, токен в файле с правами 600 и служба systemd с Restart=always. Включённый Message Content Intent и журнал journalctl решают большинство проблем. Если рядом понадобятся сайт или база данных для статистики бота, посмотрите статьи Сайт на VPS с nginx и Let’s Encrypt и Docker Compose на VPS для нескольких сервисов.
Чек-лист после запуска
Пройдите этот список один раз, прежде чем считать бота готовым. Он занимает десять минут и экономит вечер в поддержке.
- Служба
enabledиactive (running)вsystemctl status. - После
sudo rebootбот снова онлайн без ручного входа. - Токен нигде не записан в открытом виде: ни в коде, ни в истории команд, ни в git.
- Файл
.envимеет права 600 и владельцаbotuser. - В журнале нет повторяющихся ошибок за последний час:
journalctl -u discord-bot --since "1 hour ago". - Бот не имеет прав администратора, если они не нужны для работы.
- На VPS открыт только SSH, а бот не слушает входящие порты. Это можно проверить командой
sudo ss -tulpn.
Если пункт не выполняется, исправьте его до приглашения бота на большой сервер. Ошибка в правах или токене на тестовом сервере обходится дешевле, чем на сообществе из тысячи участников.
Как обновить код без простоя
Простой на время перезапуска короткий: systemctl restart занимает секунды. Но если бот обрабатывает длинные задачи, сначала дождитесь их завершения. Порядок такой: обновите файлы в каталоге, проверьте синтаксис командой ./venv/bin/python -m py_compile bot.py, затем перезапустите службу и проверьте журнал. Если ошибка, верните предыдущую версию файла из git или из резервной копии каталога.
Вопросы и ответы
Нужен ли мощный VPS для Discord-бота?
Для небольшого бота хватит самого простого тарифа: процесс на discord.py обычно занимает десятки мегабайт памяти. Больше ресурсов нужно, если бот работает с медиа, базой данных или большим числом серверов Discord. Реальное потребление смотрите командой htop или в «Мониторинге» карточки VPS.
Можно ли запускать бота на Node.js?
Да, в Discord есть библиотека discord.js. Схема та же: окружение Node, токен в .env, служба systemd с Restart=always.
Зачем отдельный пользователь botuser?
Бот не должен работать от root. Отдельный пользователь без shell ограничивает ущерб, если в коде найдётся уязвимость.
Что делать, если токен утёк?
Сбросьте его во вкладке Bot в Developer Portal, обновите .env и перезапустите службу. Старый токен после сброса перестаёт работать.
Сервер под игры за пару минут — egnetwork.ru
Читать далее
Вся рубрика →Мониторинг ресурсов VPS: htop, iotop и vnstat
Как за несколько минут понять, что тормозит VPS: процессор, память, диск или сеть. Команды htop, iotop, vnstat, free и df с примерами вывода и таблицей симптомов.
Трафик VPS: как посчитать объём и не получить сюрприз в конце месяца
Что считается трафиком, как перевести скорость канала в терабайты за месяц, как читать счётчик входящего и исходящего трафика в кабинете EGN и что делать, если лимит закончился.
IPv6 на VPS: как пользоваться префиксом /64
Что значит /64, как проверить адрес на сервере, назначить дополнительные адреса, настроить ufw для IPv6 и не выпасть из сети. Примеры команд и типичные ошибки.