MHOSTMHOST

Node.js приложение с PM2 и Nginx

Node.js-приложение, запущенное просто командой node app.js в SSH-сессии, умирает вместе с этой сессией и не переживёт падение или перезагрузку сервера. В этой статье вы установите Node.js, запустите приложение через менеджер процессов PM2 (автоперезапуск при падении, автозапуск после reboot), поставите перед ним Nginx как реверс-прокси и проверите, что всё это переживёт перезагрузку VPS.

Что понадобится

  • VPS mHost на Ubuntu 22.04/24.04, Debian 11/12 или AlmaLinux/Rocky Linux 8/9.
  • Доступ по SSH под root либо пользователем с правами sudo.
  • Код вашего Node.js-приложения уже загружен на сервер (например, через git clone или scp) и в нём есть package.json. В примерах ниже используется путь /var/www/myapp — замените на свой.
  • (опционально) домен, A-запись которого указывает на IP сервера — понадобится для server_name в Nginx. Без домена тоже можно, тогда используйте IP-адрес сервера.
Совет: команды ниже даны с sudo. Если вы уже подключены как root и sudo в системе нет — выполняйте те же команды без префикса sudo.

Шаг 1. Установите Node.js

Два стандартных способа поставить Node.js на VPS: системный пакет из репозитория NodeSource (рекомендуется для продакшена — сразу интегрируется с systemd и PM2) или nvm (удобен, если нужны несколько версий Node или установка без root).

Важно: не ставьте Node.js командой apt install nodejs / dnf install nodejs без добавления репозитория — в штатных репозиториях дистрибутивов версия Node.js обычно сильно устаревшая.

Вариант А: через NodeSource (системный Node.js)

Актуальная LTS-ветка на момент написания — Node.js 24.x. Подставьте нужную версию вместо 24, если требуется другая (например, 22 — предыдущая LTS-ветка).

Ubuntu/Debian:

bash
curl -fsSL https://deb.nodesource.com/setup_24.x -o nodesource_setup.sh
sudo -E bash nodesource_setup.sh
sudo apt-get install -y nodejs

AlmaLinux/Rocky Linux:

bash
sudo dnf module disable -y nodejs
curl -fsSL https://rpm.nodesource.com/setup_24.x -o nodesource_setup.sh
sudo bash nodesource_setup.sh
sudo dnf install -y nodejs
Совет: на AlmaLinux/Rocky по умолчанию включён встроенный модуль nodejs из AppStream — dnf module disable -y nodejs отключает его, чтобы пакет не конфликтовал с версией из NodeSource.

Проверьте установку:

bash
node -v
npm -v
Совет: вместо конкретного номера версии можно использовать setup_lts.x — он всегда указывает на текущую LTS-ветку Node.js.

Вариант Б: через nvm (без root, несколько версий Node)

nvm ставит Node.js в домашнюю директорию пользователя — без root и с возможностью держать несколько версий и переключаться между ними.

bash
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.5/install.sh | bash
Совет: на момент написания статьи актуальный тег install-скрипта — v0.40.5; свежий номер смотрите на странице релизов nvm-sh/nvm на GitHub.

Переоткройте SSH-сессию или выполните для текущего шелла (bash):

bash
source ~/.bashrc

Установите и включите текущую LTS-версию:

bash
nvm install --lts
nvm use --lts
node -v
Важно: nvm ставит Node.js только для текущего пользователя. Если планируете запускать PM2 как systemd-сервис (шаг 3), выполняйте pm2 startup под этим же пользователем — команда сама подставит правильный путь до node; скопируйте её точно как есть, не переписывая вручную.

Шаг 2. Установите PM2 и запустите приложение

Совет: в продакшене приложение часто запускают не под root, а под отдельным непривилегированным пользователем — тогда sudo нужен только для системных шагов (Node.js, Nginx, файрвол), а pm2/npm выполняются от его имени.
  1. Установите PM2 глобально:
bash
sudo npm install -g pm2
Если Node.js установлен через nvm, sudo не нужен: npm install -g pm2.
  1. Перейдите в директорию приложения и установите зависимости:
bash
cd /var/www/myapp
npm ci
Если package-lock.json в проекте нет — используйте npm install.
  1. Запустите приложение через PM2:
bash
pm2 start app.js --name myapp
Важно: приложение должно слушать только 127.0.0.1 (localhost), а не 0.0.0.0 — наружу его будет публиковать Nginx (шаг 4). Например, в Express: app.listen(3000, '127.0.0.1').
  1. Проверьте статус и логи:
bash
pm2 status
pm2 logs myapp
Совет: для повторяемого запуска (особенно если приложений несколько) удобнее описать процесс в ecosystem.config.js:
javascript
module.exports = {
  apps: [
    {
      name: "myapp",
      script: "./app.js",
      cwd: "/var/www/myapp",
      instances: 1,
      exec_mode: "fork",
      env: {
        NODE_ENV: "production",
        PORT: 3000
      }
    }
  ]
};

Запуск: pm2 start ecosystem.config.js.

Шаг 3. Сохраните список процессов и включите автозапуск PM2

Пока список процессов PM2 не сохранён и автозапуск не настроен, приложение не переживёт перезагрузку сервера.

  1. Сгенерируйте startup-скрипт:
bash
pm2 startup

PM2 определит используемую init-систему (systemd) и выведет готовую команду примерно такого вида:

bash
sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u root --hp /root

Скопируйте и выполните именно ту команду, которую PM2 вывел у вас, — путь PATH, пользователь (-u) и домашняя директория (--hp) будут подставлены под вашу систему.

  1. Сохраните текущий список процессов:
bash
pm2 save
Готово: теперь после перезагрузки сервера systemd поднимет PM2, а PM2 — все сохранённые процессы.
Совет: после любого изменения списка процессов (добавили или убрали приложение) заново выполняйте pm2 save. После обновления версии Node.js выполните pm2 unstartup, затем заново pm2 startup — иначе systemd-сервис будет ссылаться на старый бинарник node.

Шаг 4. Настройте Nginx как реверс-прокси

  1. Установите Nginx и включите автозапуск:

Ubuntu/Debian:

bash
sudo apt-get install -y nginx
sudo systemctl enable --now nginx

AlmaLinux/Rocky Linux:

bash
sudo dnf install -y nginx
sudo systemctl enable --now nginx
  1. Создайте конфиг для сайта — путь зависит от дистрибутива.

Debian/Ubuntu: sites-available/sites-enabled

bash
sudo nano /etc/nginx/sites-available/myapp

AlmaLinux/Rocky Linux: conf.d

bash
sudo nano /etc/nginx/conf.d/myapp.conf

Содержимое файла одинаковое для обоих вариантов:

nginx
server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Замените example.com на свой домен или IP сервера, 127.0.0.1:3000 — на адрес и порт, которые слушает приложение.

Для Debian/Ubuntu дополнительно включите сайт симлинком:

bash
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/

Проверьте конфигурацию и перечитайте её:

bash
sudo nginx -t
sudo systemctl reload nginx
Важно: на AlmaLinux/Rocky Linux с включённым SELinux (по умолчанию — enforcing) Nginx по умолчанию не может проксировать запросы даже на локальный TCP-порт — вы получите 502 Bad Gateway. Разрешите это один раз:
bash
sudo setsebool -P httpd_can_network_connect on
Совет: строки proxy_http_version 1.1, Upgrade и Connection "upgrade" в конфиге выше нужны, если приложение использует WebSocket (например, Socket.IO или HMR у Next.js/Vite). Если это не ваш случай, их можно убрать.

Шаг 5. Откройте порты в файрволе

Порт 80 (и 443, если позже добавите HTTPS) должен быть открыт наружу. Порт самого приложения (в примере — 3000) открывать во внешнем файрволе не нужно — к нему обращается только Nginx на этом же сервере.

Ubuntu/Debian (ufw):

bash
sudo ufw allow 80,443/tcp

Подробнее про настройку — в статье «Файрвол ufw».

AlmaLinux/Rocky Linux (firewalld):

bash
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
Важно: не открывайте порт приложения (в примере — 3000) во внешнем файрволе — весь трафик снаружи должен идти только через Nginx на 80/443.

Шаг 6. Проверьте, что всё переживёт перезагрузку

  1. Убедитесь, что и Nginx, и PM2 включены в автозапуск:
bash
systemctl is-enabled nginx
systemctl is-enabled pm2-root

Обе команды должны вывести enabled (замените root на своего пользователя, если pm2 startup выполняли не под root).

  1. Проверьте на деле — перезагрузите сервер:
bash
sudo reboot

Подождите минуту-две, подключитесь по SSH заново и проверьте:

bash
pm2 status
curl -I http://127.0.0.1:3000
curl -I http://localhost/
systemctl status nginx

Если pm2 status показывает приложение в статусе online, а оба curl возвращают HTTP-ответ — всё настроено верно.

Готово: приложение доступно через Nginx по вашему домену или IP и переживёт любую перезагрузку сервера.

Что дальше

Дальше обычно добавляют HTTPS (например, через Let's Encrypt/certbot) и открывают правило для 443 в файрволе (см. шаг 5). Если на сервере несколько приложений — заведите под каждое отдельный конфиг Nginx и отдельную запись в ecosystem.config.js, и не забывайте про pm2 save после любых изменений списка процессов.