Все статьи
Руководства

Как запустить Node.js-приложение на VPS: PM2, nginx и systemd

Коротко

Чтобы Node.js-приложение работало на VPS круглосуточно, установите актуальную LTS-версию Node.js из репозитория NodeSource, заведите для приложения отдельного пользователя, вынесите секреты в .env и запустите процесс под PM2: pm2 start, затем pm2 save и pm2 startup. Снаружи на портах 80 и 443 работает nginx, а новый код выкатывается без простоя командой pm2 reload.

Главное
  • Node.js на сервер ставят из репозитория NodeSource или через nvm: пакет nodejs из стандартного репозитория Ubuntu и Debian обычно заметно старше актуальной LTS-версии.
  • PM2 держит Node.js-приложение запущенным круглосуточно: pm2 start запускает процесс, pm2 save запоминает список процессов, pm2 startup поднимает их после перезагрузки сервера.
  • Node.js-приложение слушает локальный порт (например, 3000), а наружу на портах 80 и 443 смотрит nginx как обратный прокси - в нём же настраивается HTTPS.
  • pm2 reload в режиме cluster перезапускает процессы Node.js по одному, поэтому обновление кода проходит без простоя, а pm2 restart и systemctl restart дают короткий перерыв.
  • Небольшому API или сайту на Node.js хватает VPS с 1 vCPU и 2 ГБ памяти - на Tihost такая конфигурация стоит $4.00 в месяц.

Шаг 1. Установите актуальную LTS-версию Node.js

LTS (Long Term Support) - ветка Node.js с долгой поддержкой и исправлениями безопасности, для сервера берут именно её. Основной путь - репозиторий NodeSource: скрипт setup_lts.x подключает его для текущей LTS-ветки, после чего Node.js и npm ставятся и обновляются обычным apt. Скрипт сначала скачивается в файл - его можно прочитать перед запуском:

bash
sudo apt update
sudo apt install -y ca-certificates curl git
curl -fsSL https://deb.nodesource.com/setup_lts.x -o nodesource_setup.sh
sudo -E bash nodesource_setup.sh
sudo apt install -y nodejs
node -v
npm -v

Второй путь - nvm (Node Version Manager): он ставит Node.js в домашний каталог пользователя командой nvm install --lts и удобен, когда на одном сервере нужны разные версии. Минус для сервера - путь к node зависит от пользователя и версии, и его приходится прописывать в systemd и cron вручную. Номера LTS-веток и график поддержки - на nodejs.org.

Шаг 2. Создайте отдельного пользователя для приложения

Приложение не должно работать от root: если в нём найдут уязвимость, атакующий получит права только этого пользователя. Пользователь app без пароля не войдёт по SSH сам, а вы переключаетесь в него через sudo -iu app:

bash
sudo adduser --disabled-password --gecos "" app
sudo -iu app

Шаг 3. Загрузите код приложения

Код удобнее всего доставлять через git: клонируете репозиторий один раз, дальше обновляете git pull. npm ci ставит зависимости строго по package-lock.json, а npm run build --if-present собирает проект, если в нём есть скрипт build (TypeScript, Next.js, Nest):

bash
git clone https://github.com/you/myapp.git ~/myapp
cd ~/myapp
npm ci
npm run build --if-present

Для приватного репозитория создайте пользователю app SSH-ключ (ssh-keygen -t ed25519) и добавьте публичную часть в репозиторий как deploy key только на чтение - так серверу не нужен ваш личный пароль или токен. Подробнее о ключах - в статье про подключение к VPS по SSH.

Шаг 4. Вынесите настройки и секреты в .env

Пароли, токены и адреса баз данных не хранят в коде и в git - их кладут в файл .env, который может прочитать только владелец. Добавьте .env в .gitignore, чтобы он случайно не попал в репозиторий:

bash
cat > ~/myapp/.env <<'EOF'
NODE_ENV=production
PORT=3000
DATABASE_URL=postgres://myapp:password@localhost:5432/myapp
EOF
chmod 600 ~/myapp/.env

Приложение должно само прочитать .env: чаще всего это пакет dotenv (npm install dotenv и require("dotenv").config() первой строкой), а в Node.js 20.6 и новее есть встроенный флаг --env-file=.env. При запуске через systemd файл подставляет сам systemd - об этом ниже. Если нужна база данных, её установка и настройка доступа описаны в статье про PostgreSQL на VPS.

Шаг 5. Запустите приложение под PM2

PM2 - менеджер процессов для Node.js: он перезапускает упавшее приложение, собирает логи и умеет перезагружать процессы по одному. Поставьте его глобально от пользователя с sudo:

bash
sudo npm install -g pm2

Запуск, сохранение списка процессов и автозапуск. -i 2 включает режим cluster с двумя процессами - это нужно для обновления без простоя даже на сервере с 1 vCPU. pm2 startup создаёт сервис systemd pm2-app, который после перезагрузки сервера восстановит процессы из pm2 save:

bash
sudo -iu app
cd ~/myapp
pm2 start server.js --name myapp -i 2
pm2 save
exit
sudo pm2 startup systemd -u app --hp /home/app

Альтернатива PM2: сервис systemd

Если одного процесса достаточно, PM2 можно не ставить: systemd уже есть в Ubuntu и Debian и делает то же самое - запускает приложение при загрузке и перезапускает через 5 секунд после падения. EnvironmentFile передаёт переменные из .env в окружение процесса, dotenv при этом не нужен. Минус - systemctl restart останавливает приложение на время перезапуска:

bash
sudo tee /etc/systemd/system/myapp.service > /dev/null <<'EOF'
[Unit]
Description=Node.js app
After=network-online.target
Wants=network-online.target

[Service]
User=app
WorkingDirectory=/home/app/myapp
EnvironmentFile=/home/app/myapp/.env
ExecStart=/usr/bin/node server.js
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now myapp

Шаг 6. Проверьте статус и логи

pm2 status показывает процессы, их память и число перезапусков, pm2 logs - вывод приложения. Модуль pm2-logrotate ротирует логи, чтобы они не заняли весь диск. Для варианта с systemd то же самое дают systemctl status myapp и journalctl -u myapp -f. Команды PM2 выполняйте от пользователя app:

bash
pm2 status
pm2 logs myapp --lines 100
pm2 install pm2-logrotate

Шаг 7. Поставьте nginx перед приложением

Обратный прокси (reverse proxy) принимает запросы из интернета и передаёт их приложению на локальный порт. nginx берёт на себя HTTPS, сжатие, статику и лимиты, а порт 3000 остаётся закрытым снаружи. Заголовки Upgrade и Connection нужны для WebSocket. Сохраните конфиг как /etc/nginx/sites-available/myapp, заменив example.com на свой домен:

/etc/nginx/sites-available/myapp
server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        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;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}
bash
sudo apt install -y nginx
sudo nano /etc/nginx/sites-available/myapp
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo ufw allow 80,443/tcp

Домен должен указывать на IP сервера - как это настроить, описано в статье про привязку домена к VPS. Сертификат Let's Encrypt и редирект на HTTPS добавляются за пару команд - по инструкции nginx и Let's Encrypt.

Шаг 8. Обновляйте приложение без простоя

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

bash
sudo -iu app
cd ~/myapp
git pull
npm ci
npm run build --if-present
pm2 reload myapp

Какой VPS нужен Node.js-приложению

API, Telegram-боту или небольшому сайту на Node.js хватает 1 vCPU и 2 ГБ памяти - на Tihost это конфигурация Starter за $4.00 в месяц. Если на том же сервере работают база данных и сборка фронтенда, берите 4 ГБ и больше: подробнее о расчёте - в статье сколько оперативной памяти нужно VPS. Серверы Tihost работают на KVM с NVMe и полным root-доступом и разворачиваются примерно за 2 минуты после оплаты.

Запустите сервер за 2 минуты

AMD Ryzen 9, NVMe и защита от DDoS в Германии, Финляндии и Польше. Оплата криптовалютой или картой.

Заказать сервер

Частые вопросы

Что лучше для Node.js на сервере - PM2 или systemd?

PM2 удобнее, когда нужны режим cluster и обновление без простоя через pm2 reload. systemd уже встроен в Ubuntu и Debian и проще для одного процесса, но systemctl restart даёт короткий перерыв в работе.

Как поставить Node.js LTS на Ubuntu?

Подключите репозиторий NodeSource скриптом setup_lts.x и выполните sudo apt install -y nodejs. Альтернатива - nvm и команда nvm install --lts в домашнем каталоге пользователя.

Почему Node.js-приложение останавливается, когда я закрываю SSH?

Приложение, запущенное командой node server.js из терминала, завершается вместе с SSH-сессией. Запустите его под PM2 или как сервис systemd - тогда оно работает независимо от SSH и поднимается после перезагрузки сервера.

Нужен ли nginx, если Node.js сам умеет слушать порт?

nginx перед Node.js-приложением отвечает за HTTPS, сжатие, статику и несколько сайтов на одном IP, а порт приложения остаётся закрытым снаружи. Без nginx пришлось бы запускать Node.js с правами на порты 80 и 443 и настраивать сертификаты в коде.

Как обновить Node.js-приложение без простоя?

Запустите приложение под PM2 в режиме cluster (pm2 start server.js -i 2), а после git pull и npm ci выполните pm2 reload myapp: PM2 перезапустит процессы по одному, и запросы продолжат обслуживаться.

Сколько памяти нужно Node.js-приложению на VPS?

Небольшому API или боту на Node.js хватает VPS с 2 ГБ памяти. Если рядом работает база данных или на сервере собирается фронтенд, удобнее 4-8 ГБ.