Telegram-бот на Node.js готов и работает локально: запущен node bot.js, отвечает на сообщения. Но локальный запуск живёт ровно до закрытия терминала или выключения ноутбука. Чтобы бот отвечал круглосуточно, его нужно разместить там, где процесс работает постоянно. Разберём варианты хостинга, подготовку к деплою и сам деплой. Версия этой статьи для Python-бота — рядом.
Long-polling или webhook
Это первое решение, и от него зависит, какой хостинг подойдёт.
Long-polling — бот сам постоянно опрашивает Telegram методом getUpdates. Ему не нужен публичный домен, SSL или открытый входящий порт — достаточно исходящего соединения в интернет. В Telegraf это режим по умолчанию (bot.launch()), в grammY — bot.start(). Для большинства ботов его более чем достаточно.
Webhook — наоборот: Telegram сам присылает обновления на HTTPS-эндпоинт бота. Нужны публичный домен, валидный SSL-сертификат и открытый порт (Telegram принимает только 443, 80, 88 или 8443). Webhook эффективнее под высокой нагрузкой и удобнее, когда нужно несколько инстансов за балансировщиком.
Практический вывод: начинать стоит с long-polling, а переходить на webhook — когда нагрузка реально этого требует. Важная деталь: одновременно работает либо одно, либо другое. Два параллельных long-polling-процесса с одним токеном дадут 409 Conflict: terminated by other getUpdates request.
Куда размещать: три варианта
VPS (Beget, Timeweb, Hetzner). Полный контроль над сервером. Минус — всё придётся настроить и обслуживать самому: установить Node.js, поднять менеджер процессов для автозапуска, развернуть базу, настроить бэкапы и SSL для webhook. Дёшево по деньгам, дорого по времени.
Managed-платформа (PaaS). Деплой по git push: платформа сама собирает проект, держит процесс запущенным и перезапускает его после сбоя, рядом — managed-база. Меньше ручной работы ценой меньшей гибкости в настройке ОС.
Serverless (облачные функции). Для ботов обычно не подходит. Long-polling требует постоянно живущего процесса, которого в serverless нет; webhook-функции возможны, но страдают от cold start и лимитов времени выполнения. Для бота, который должен быть онлайн всегда, это неудобная модель.
Подготовка бота к деплою
Эти шаги нужны при любом хостинге.
Зафиксировать зависимости. Зависимости должны быть перечислены в package.json, а package-lock.json — закоммичен: по нему npm ci ставит точные версии. Сам каталог node_modules в репозиторий не кладут — он указывается в .gitignore.
Вынести токен и секреты из кода. Токен бота нельзя держать в исходниках и тем более коммитить в Git — его читают из переменных окружения:
const TOKEN = process.env.BOT_TOKEN;
if (!TOKEN) throw new Error("BOT_TOKEN is not set");
Локально переменные удобно держать в файле .env (через пакет dotenv или встроенный флаг node --env-file=.env bot.js в Node.js 20.6+) и добавить .env в .gitignore. На сервере те же переменные задаются в окружении процесса. Если токен всё же попал в публичный репозиторий — его нужно немедленно отозвать через @BotFather и выпустить новый.
Не хранить состояние в памяти. Данные пользователей, сессии и очереди при хранении в обычных переменных JavaScript теряются при каждом рестарте и редеплое. Под состояние нужна внешняя база — Postgres или Redis. О резервных копиях такой базы — в статье про бэкапы PostgreSQL.
Деплой на VPS
Кратко: забрать код, установить зависимости, запустить под менеджером процессов.
git clone https://github.com/user/mybot.git /opt/mybot
cd /opt/mybot
npm ci # точные версии из package-lock.json
Запускать node bot.js руками нельзя — процесс умрёт с закрытием сессии. Для Node.js идиоматичен pm2, который держит бот запущенным и поднимает после сбоя:
npm install -g pm2
pm2 start bot.js --name mybot
pm2 startup # зарегистрировать автозапуск при перезагрузке
pm2 save # зафиксировать список процессов
Без pm2 save после перезагрузки сервера список окажется пустым. Полная настройка pm2, systemd и Docker restart — в статье как держать процесс запущенным 24/7.
Деплой на managed-платформе
Сценарий короче: подключить репозиторий, задать переменные окружения (включая BOT_TOKEN) в панели и сделать git push. Платформа определит по package.json, что это Node.js-проект, выполнит npm ci, запустит стартовый скрипт (scripts.start, например node bot.js) и будет держать процесс живым — без отдельной настройки менеджера процессов. Managed-база, а для webhook домен с SSL, поднимаются рядом и не требуют ручной возни.
Проверка после деплоя
Бот задеплоен — но «задеплоен» не значит «работает». Стоит убедиться явно:
- посмотреть логи процесса: при старте не должно быть исключений, а long-polling-бот сообщает, что начал получать обновления;
- отправить боту
/startв Telegram и дождаться ответа; - проверить, что токен виден процессу — метод
getMeвозвращает данные бота, а не ошибку401 Unauthorized:
curl https://api.telegram.org/bot<BOT_TOKEN>/getMe
Если бот молчит, причина почти всегда одна из двух: не задан BOT_TOKEN или запущен второй инстанс, перехвативший long-polling.
Частые грабли
- Два запущенных инстанса. Старый процесс на VPS плюс новый редеплой — и оба зовут
getUpdates, в ответ409 Conflict. Long-polling-консьюмер должен быть один. node_modulesв репозитории. Каталог раздувает репозиторий и ломает сборку на другой ОС. В Git едутpackage.jsonиpackage-lock.json, а неnode_modules.- Состояние в памяти. После редеплоя «бот всё забыл» — данные жили в переменных, а не в базе.
- Токен в репозитории. Любой, кто видит код, видит токен. Отзывать через
@BotFather. - Free-tier, который усыпляет процесс. Бот «иногда не отвечает», потому что хостинг остановил неактивный процесс. Боту нужен постоянно живущий процесс.
Чеклист
- Выбран режим: long-polling по умолчанию или webhook под нагрузку.
- Зависимости в
package.json,package-lock.jsonзакоммичен,node_modules— в.gitignore. - Токен и секреты — в переменных окружения, не в коде и не в Git.
- Состояние хранится в Postgres или Redis, а не в памяти.
- Процесс запущен под
pm2или другим супервизором с автозапуском, а неnode bot.js. - Запущен ровно один long-polling-инстанс.
- Настроены бэкапы базы.
Managed-платформа закрывает большую часть этого списка по умолчанию: процесс держится сам, managed Postgres идёт с ежедневными бэкапами, а для webhook домен и SSL поднимаются автоматически. На Hostim Node.js-бот разворачивается по git push и работает 24/7 без serverless-засыпания — переменные окружения задаются в панели, база подключается рядом.