Развёртывание Node.js-приложения на VPS с PM2: практическое руководство

Развёртывание Node.js-приложения на VPS с PM2: практическое руководство

Как настроить Node.js-приложение на VPS так, чтобы оно работало стабильно и без сюрпризов? PM2, мониторинг, автоматические перезапуски — собрали всё в одной инструкции!

Когда стартап впервые выкатывает API, обычно хватает одной команды — node app.js — чтобы запустить сервис. Пока трафик измеряется десятками запросов в минуту, всё идёт гладко: процесс держится в памяти, а логи выводятся в терминал. Как только нагрузка возрастёт или в код прокрадётся TypeError, приложение аварийно завершится, клиенты получат «502 Bad Gateway», а разработчик в панике будет наблюдать бесконечную бегущую строку в консоли.

В этом материале рассказали, как правильно настроить приложение на VPS, чтобы избежать таких сбоев: как развернуть актуальную LTS-версию Node.js, установить PM2 через npm, подключить его к systemd для автозапуска, настроить ротацию логов, экспортировать метрики в Prometheus и добиться бесшовного перезапуска без потери соединений.

Аренда VPS/VDS от 219 руб/месяц

Преимущества VPS в AdminVPS:

✓ Бесплатное администрирование

✓ Только быстрые NVMe-диски

✓ Защита от DDoS-атак

✓ Быстрая техподдержка

Аренда VPS/VDS виртуального сервера от AdminVPS — это прозрачная и честная услуга с доступной ценой

Зачем нужен отдельный процесс-менеджер

Node.js обрабатывает все запросы в единственном потоке. Если внутри цикла событий происходит необработанное исключение или переполнение памяти, процесс завершается — клиентские подключения обрываются, а стандартный запуск node их не восстанавливает. На практике это означает потерянные транзакции и ошибки оплаты даже при кратком сбое.

PM2 решает проблему в два этапа: он поддерживает таблицу процессов и мгновенно поднимает новый экземпляр, когда предыдущий выходит с ненулевым кодом, а также сохраняет эту таблицу на диск и интегрирует её с systemd. После перезагрузки VPS список приложений восстанавливается автоматически, без ручного вмешательства администратора.

Дополнительно PM2 умеет передавать открытый сокет между версиями приложения, поэтому обновление кода проходит без разрыва активных соединений. Журналы stdout и stderr пишутся раздельно, встроенный модуль сжимает их в архивы и удаляет старые файлы, чтобы не переполнить диск. При росте нагрузки достаточно запустить приложение с параметром --instances max, и менеджер создаст столько воркеров, сколько ядер доступно, равномерно распределяя запросы.

Подготовка VPS: обновления, учётная запись, базовая защита

Допустим, вы только что арендовали виртуальный сервер с Ubuntu 22.04 LTS и подключились к нему по SSH. Консоль приветствует вас строкой Welcome to Ubuntu, но это не гарантирует, что ядро и библиотеки свежие. Первая задача — подтянуть пакеты безопасности. Введите:

sudo apt update && sudo apt upgrade -y

Где apt update спрашивает у репозиториев, какие версии доступны, а apt upgrade устанавливает их. Ключ -y избавляет от лишних вопросов, иначе система будет ждать вашего подтверждения после каждой библиотеки. Завершив обновление, перезагрузите сервер командой sudo reboot, чтобы новое ядро стало активным.

Следующий шаг — переход от всесильного root к обычному пользователю. Работать под root опасно: одна опечатка rm -rf / может стереть систему. Создайте новую учётную запись и дайте ей права администратора:

sudo adduser deploy
sudo usermod -aG sudo deploy

Теперь настройте вход по ключу. На домашнем компьютере выполните ssh-keygen без пароля, а затем:

ssh-copy-id deploy@<IP-VPS>

С этого момента сервер пустит вас только с тем самым публичным ключом. Чтобы окончательно отключить пароли, откройте файл

/etc/ssh/sshd_config

Найдите строку:

PasswordAuthentication yes

Замените на no и перезапустите демон:

sudo systemctl restart sshd

Если ошибётесь в конфигурации, текущая сессия останется активной, поэтому вы успеете поправить файл.

Пока сервер ещё голый, закройте ненужные порты. Ubuntu поставляется с удобным брандмауэром ufw, включите его командой:

sudo ufw default deny incoming
sudo ufw allow ssh
sudo ufw enable

Здесь: default deny incoming запрещает всё, что не разрешено явно, allow OpenSSH разрешает порт 22. Когда приложение будет слушать, например, 3000-й порт, добавьте правило:

sudo ufw allow 3000/tcp

Проверить список открытых портов можно так:

sudo ufw status numbered

Чтобы защитить SSH от автоматического подбора паролей (брутфорса), достаточно установить Fail2Ban и запустить его службу. В файле:

/etc/fail2ban/jail.d/ssh.conf

По умолчанию уже есть правило, блокирующее IP после пяти неверных попыток входа.

Убедитесь, что на сервере выставлено точное время: задержка даже в пару минут может привести к тому, что валидный TLS-сертификат будет считаться просроченным, а JWT-токены отклоняться. Проверьте, что служба синхронизации времени запущена командой:

timedatectl status

Если требуется другой часовой пояс, задайте его:

sudo timedatectl set-timezone Russia/Moscow

Установка Node.js нужной версии

Node.js живёт по чёткому расписанию: каждый октябрь выходит новая основная ветка, а предыдущая становится LTS — «долго-живущей». Разработчики поддерживают такую ветку три года, публикуя ежемесячные патчи с пометкой Security или Performance. Обновление приходит по тем же каналам, что и для OpenSSL, поэтому администратору не нужно мониторить отдельный блог.

Перед запуском проверьте на сайте NodeSource, какая ветка сейчас имеет статус LTS. Если, например, актуальной стала 24-я, замените значение «22» в ссылке setup_22.x на соответствующий номер. Чтобы получить именно LTS, подключите репозиторий NodeSource. Скрипт ниже скачает ключ подписи, положит .list-файл в:

/etc/apt/sources.list.d

и обновит кеш пакетов:

curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs

Команда node -v выдаст что-то вроде v22.14.0. В тот же пакет входит npm, проверяем его наличие — npm -v. Если вместо цифр сервер ругается command not found, проверьте, не забыли ли вы добавить /usr/bin в переменную PATH.

Иногда проекту требуется сразу две версии Node.js — старая для монолита, новая для микросервиса. В таких случаях удобно установить nvm в домашний каталог пользователя deploy.

Запустите:

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash

Затем выполните команду, чтобы текущая сессия «увидела» nvm:

source ~/.nvm/nvm.sh

Теперь можно установить нужную версию:

nvm install 24  
nvm use 24

Команда node -v покажет 24.x, а переключиться обратно на LTS можно через nvm use 22.

Утилита nvm не меняет системный /usr/bin/node, поэтому скрипты cron, работающие от root, продолжат использовать стабильную ветку.

Ещё одна тонкость: некоторые npm-пакеты содержат модули на C, которые при установке преобразуются в машинный код (компилируются) — без установленного компилятора процесс прервётся ошибкой «make: not found». Поставьте набор инструментов однажды:

sudo apt install -y build-essential

На сервере с 512 МБ оперативной памяти Node.js по умолчанию резервирует под heap почти половину объёма, из-за чего при пиковых нагрузках системный OOM-киллер может завершить процесс. Чтобы этого не допустить, задайте ограничение через переменную окружения, например:

export NODE_OPTIONS="--max-old-space-size=256"

Добавьте эту строку в /etc/environment и перезагрузите оболочку.

Установка и запуск PM2

Версию PM2 из стандартных репозиториев Ubuntu и Debian замораживают при выпуске дистрибутива, поэтому она быстро устаревает и не получает свежих исправлений. Установка из npm лишена этого недостатка: пакет обновляется сразу после релиза, сохраняя совместимость с LTS-веткой Node.js. Достаточно одной команды:

sudo npm install -g pm2

Исполняемый файл PM2 попадает в /usr/bin, уже включённый в PATH, то есть команду pm2 можно запускать откуда угодно. Чтобы убедиться, что всё установилось, наберите в терминале pm2 -v — это выведет текущую версию PM2. Обновление до самой свежей версии выполняется той же командой:

npm install -g pm2@latest

Здесь npm — это менеджер пакетов для Node.js, опция -g означает глобальную установку, а @latest указывает на самый последний релиз.

Теперь подготовим проект. Пусть код хранится на GitHub, а работать мы будем от имени пользователя deploy:

cd /home/deploy
git clone git@github.com:username/myapi.git
cd myapi
npm ci

Здесь npm ci устанавливает зависимости строго по package-lock.json. Переменные окружения (ключи API, строка подключения к базе) храните в .env и не добавляйте этот файл в репозиторий.

Запускаем сервис под управлением PM2:

pm2 start app.js --name myapi --env production

Флаг --name присваивает процессу понятное имя, чтобы его было легко найти в списке. Параметр --env production внутри приложения устанавливает NODE_ENV=production, то есть говорит ему работать в боевом режиме. После запуска проверьте статус всех процессов командой pm2 list. В колонке status у нужного процесса должно быть «online». А чтобы посмотреть текущие логи конкретного приложения, выполните команду:

pm2 logs myapi. myapi

Здесь myapi — это имя вашего процесса, заданное через --name.

Чтобы приложение стартовало после перезагрузки VPS, интегрируем PM2 с systemd:

pm2 startup systemd -u deploy --hp /home/deploy
pm2 save

Здесь pm2 startup создаёт сервис-юнит; pm2 save сохраняет текущий список процессов. После этого даже экстренный ребут сервера не прервёт работу myapi: systemd поднимет PM2, а менеджер восстановит службу автоматически.

Управление жизненным циклом с PM2

При обновлении кода важно, чтобы пользователи не замечали перезапуска процессов. Команда pm2 reload решает задачу: менеджер поднимает вторую копию приложения, проверяет, что она успешно забрала порт, и только затем завершает старую. Полный цикл обновления конфигурации или зависимостей сводится к одной строке:

git pull && npm ci && pm2 reload myapi --update-env

Параметр --update-env передаёт новому процессу свежие переменные окружения, не влияя на текущие соединения.

Лог-ротация без внешних скриптов

Журналы out.log и error.log могут вырасти до сотен мегабайт за несколько дней. Встроенный модуль pm2-logrotate обрезает и сжимает их без участия cron. Подключите расширение:

pm2 install pm2-logrotate

Затем настройте предел размера, число архивов и сжатие:

pm2 set pm2-logrotate:max_size 100M
pm2 set pm2-logrotate:retain 10
pm2 set pm2-logrotate:compress true
pm2 save

После перезапуска PM2 новые параметры попадут в дамп, который systemd читает при каждом старте. Логи больше не переполнят диск, а поиск ошибок упростится: разверните последний архив и просмотрите его через grep.

Настройка метрик и алертов

Иллюстрация: настройка алертов

Когда приложение работает под PM2, важно следить за его состоянием: потреблением памяти, задержкой event-loop и частотой перезапусков. Встроенный агент PM2 с флагом --metrics формирует поток данных (JSON) на локальный порт 9615. Экспортёр pm2-prometheus-exporter переводит эти метрики в формат Prometheus, после чего их можно сразу подключить к Grafana.

На дашборде отображаются ключевые показатели: объём памяти (RSS), частота сборок мусора, глубина очереди событий и счётчик рестартов. Обычно алерт срабатывает, если процесс перезапускается трижды за 10 минут. Для event-loop можно задать правило: если задержка стабильно выше 100 мс в течение пяти минут, скорее всего, приложение столкнулось с синхронным кодом или медленной базой данных.

Уведомления отправляются через Alertmanager в Slack или Telegram. Чтобы облегчить разбор инцидента, добавьте в сообщение ссылку на pm2 logs и график Grafana за последние три часа — это позволяет открыть логи и увидеть рост нагрузки буквально одной кнопкой.

Продвинутая оптимизация: graceful shutdown и обработчик SIGINT

PM2 завершает процесс корректно: перед остановкой он посылает сигнал SIGINT. Чтобы запросы не обрывались, приложению нужно корректно закрыть открытые порты и соединения с базой.

Добавьте обработчик:

process.on('SIGINT', async () => {
    try {
        await server.close();
        await pool.end();
    } catch (err) {
        console.error('Ошибка при завершении приложения:', err);
    } finally {
        process.exit(0);
    }
});

Команда server.close() позволяет Node.js сначала обслужить все текущие запросы. Новые подключения в это время уже принимает второй экземпляр, который PM2 поднял командой reload.

Если приложение использует несколько портов или WebSocket-подписок, храните их в массиве servers и закрывайте так:

await Promise.all(servers.map(s => s.close()));

Чтобы избежать петли, когда процесс бесконечно падает и тут же запускается заново, установите задержку с постепенным увеличением времени между рестартами:

pm2 start app.js --name myapi --exp-backoff-restart-delay=200

Параметр --exp-backoff-restart-delay задаёт начальную задержку в миллисекундах между аварийными перезапусками. В данном примере процесс подождёт 200 мс перед первым рестартом, затем постепенно увеличит паузу, если сбои продолжатся.

Каждый новый аварийный выход увеличит паузу, сервис остынет, а очередь запросов разрядится.

После любых изменений сохраняйте конфигурацию pm2 save. Файл ~/.pm2/dump.pm2 держите в регулярном бекапе вместе с репозиторием: при сбое диска менеджер восстановит все процессы одной командой pm2 resurrect.

Keymetrics — это облачная панель, которая помогает следить за приложениями, запущенными через PM2. Достаточно подключить сервер, чтобы увидеть тепловую карту нагрузки (какие процессы нагружены сильнее всего) и начать получать уведомления в Telegram, если процесс начал расходовать слишком много памяти или система зафиксировала критическую ошибку (panic).

В бесплатной версии доступны основные уведомления и история работы за последние 24 часа. Этого обычно хватает, чтобы заметить проблемы на ранней стадии и исправить их до того, как начнут жаловаться пользователи.

Проверка устойчивости до релиза

Перед публикацией поднимите локальный wrk и дайте приложению двести параллельных соединений, постепенно увеличивая время теста до десяти минут. Одновременно расширьте ulimit -n до двадцати тысяч, чтобы убедиться, что система способна открыть нужное количество файловых дескрипторов. Следом остановите базу данных: если используется PostgreSQL в Docker-контейнере, достаточно выполнить:

docker stop postgres

PM2 обязан зафиксировать рост счётчика рестартов, а после возобновления БД приложение должно вернуться в состояние online, сохранив все воркеры. Завершите испытание симуляцией перегрева — запустите фоновый stress --cpu 4, поднимите температуру ядра, посмотрите, не начинает ли event-loop стабильно превышать 100 миллисекунд. Только убедившись, что алерты отработали, а время отклика вернулось к норме, стоит переносить код в продуктивную ветку.

Заключение

В итоге даже небольшая виртуальная машина может стать надёжной площадкой для запуска Node.js-приложения. Грамотно настроенный стек от автозапуска и обновлений до логирования и мониторинга помогает избегать сбоев, заранее видеть возможные проблемы и спокойно управлять сервисом без авралов и ночных звонков.

Читайте в блоге:

Loading spinner
0 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии

VPN на VPS-сервере

Узнайте, как создать собственный VPN на VPS-сервере для защиты ваших конфиденциальных данных!

Что будем искать? Например,VPS-сервер

Мы в социальных сетях