DevOps и Docker 12 мин чтения 2026-09-29

Развертывание Node.js и Next.js на VDS: настройка связки PM2, Nginx Reverse Proxy и автопродления SSL

Подробная инструкция по продакшен-деплою Node.js и Next.js на сервере Ubuntu: кластерный запуск через PM2, изоляция портов, конфигурация Nginx Reverse Proxy с кэшированием статики, выпуск SSL-сертификатов и скрипт бесшовного обновления.

Инженерная редакция SysKit Проверено инженерами SysKit.ru

Фреймворк Next.js и среда выполнения Node.js стали стандартом разработки современных веб-приложений, интерактивных клиентских сервисов и личных кабинетов. Однако этап переноса локально написанного проекта на сервер VDS часто вызывает у вебмастеров и разработчиков сложности: облачные PaaS-платформы вроде Vercel требуют регулярных платежей в валюте, блокируют аккаунты или жестко ограничивают лимиты запросов на бесплатных тарифах.

Запуск приложения напрямую через npm run start или node server.js в окне SSH-терминала — распространенная ошибка начинающих специалистов. При закрытии консоли сессия обрывается, в случае необработанной ошибки Node.js процесс падает без автоматического перезапуска, а при перезагрузке операционной системы сайт остается недоступным. Кроме того, встроенный веб-сервер Node.js не предназначен для прямой отдачи тяжелой статики в интернет без обратного прокси и средств защиты от сетевых атак.

В этом практическом руководстве мы разберем production-стек деплоя: связку отказоустойчивого менеджера процессов PM2, высокопроизводительного обратного прокси-сервера Nginx и автоматического SSL-сертификата Let's Encrypt на сервере с Ubuntu 24.04 LTS.

1. Архитектура продакшен-стека: почему Node.js нужен Nginx и PM2

Для надежной работы веб-приложения на VDS роли между компонентами разделяются следующим образом:

  • PM2 (Process Manager): Выполняет роль демонизатора и супервизора Node.js. Запускает приложение в фоновом режиме, перезапускает процесс при сбоях или исчерпании лимита памяти, собирает логи и распределяет нагрузку по ядрам процессора в кластерном режиме (Cluster Mode).
  • Nginx (Reverse Proxy): Принимает внешние HTTP/HTTPS запросы на портах 80 и 443, берет на себя тяжелую нагрузку по TLS-терминации, напрямую отдает статические файлы сборки (папки /_next/static/ и /public/) без привлечения движка V8, сжимает трафик через gzip/brotli и проксирует динамические запросы на внутренний сокет или локальный порт 127.0.0.1:3000.
  • Systemd & Certbot: Обеспечивают автоматический подъем менеджера PM2 при перезагрузке виртуального сервера и своевременное продление криптографических сертификатов SSL без участия человека.

Совет инженера по производительности

Отдача статических файлов напрямую через Nginx экономит до 60% оперативной памяти и ресурсов CPU сервера. Движок V8 в Node.js однопоточен, и перекладывание рутины по чтению CSS, JavaScript-чанков и изображений на асинхронный Nginx освобождает воркеры приложения для быстрой генерации динамического SSR-контента.

2. Требования к серверу VDS и сайзинг ресурсов под Next.js

Специфика сборки (build) и работы (runtime) Next.js заключается в том, что процессу компиляции Webpack/Turbopack требуется кратковременно в 2–3 раза больше оперативной памяти, чем работающему приложению в режиме обслуживания посетителей:

Сценарий проекта Процессор Память (RAM) Накопитель Режим PM2
Лендинг / Простой сайт 1 vCPU (3.0+ ГГц) 1 ГБ RAM + 2 ГБ Swap 15–20 ГБ NVMe fork (1 инстанс)
Интернет-магазин / SSR 2 vCPU 2–4 ГБ RAM 30–50 ГБ NVMe cluster (-i max)
Высоконагруженный сервис / SaaS 4+ vCPU 8+ ГБ RAM 80+ ГБ NVMe cluster (4 инстанса)

Математика памяти в кластерном режиме PM2

Каждый воркер кластера PM2 — это изолированный процесс Node.js со своим собственным движком V8 и выделенной памятью. Если у вас VDS с 2 vCPU и вы запускаете 2 инстанса с лимитом 350 МБ, только на процессы Node.js потребуется до 700 МБ RAM. На серверах с 1 vCPU и 1 ГБ RAM настоятельно рекомендуется задавать instances: 1 (режим fork), а кластерный режим масштабирования подключать на тарифах от 2 ГБ RAM.

Опасность сбоев OOM Killer при выполнении npm run build

Команда сборки Next.js (next build) на этапе генерации страниц и статической оптимизации активно потребляет память. Если ваш VDS имеет 1 ГБ RAM, компилятор неизбежно упадет с ошибкой JavaScript heap out of memory или процесс будет аварийно убит системным демоном Linux OOM Killer. Обязательно создайте Swap-файл минимум на 2 ГБ перед первой сборкой на сервере.

3. Надежные VDS-провайдеры для размещения Node.js и Next.js приложений

Для бесперебойной работы Node.js критически важна высокая однопоточная производительность vCPU (частота от 3.3 ГГц) и скоростные диски NVMe для быстрой компиляции модулей из node_modules.

Рекомендуемые VDS-провайдеры

Отказоустойчивые сервера с быстрыми NVMe и каналом до 1 Гбит/с

Выбор редакции
⚡

Timeweb Cloud — Быстрые NVMe VDS

Высокочастотные процессоры 3.3+ ГГц для мгновенной компиляции Next.js, быстрые NVMe-диски и безлимитный трафик 1 Гбит/с.

Конфигурация
1 vCPU 3.3 ГГц • 1 ГБ RAM • 15 ГБ NVMe • Трафик без лимита
Премиум Tier III
🔷

Selectel — Надежные облачные серверы

Высокая отказоустойчивость дата-центров Tier III, гарантированные ядра vCPU для SSR-рендеринга и легкое масштабирование ресурсов.

Конфигурация
2 vCPU • 2 ГБ RAM • 30 ГБ NVMe • Дата-центры Tier III
Простота и стабильность
🟠

Beget — Удобно для веб-разработчиков

Удобная панель управления сервером, надежная KVM-виртуализация, бесплатные ежедневные бэкапы и мгновенный запуск за 1 минуту.

Конфигурация
1 vCPU • 1 ГБ RAM • 15 ГБ NVMe • Бесплатные бэкапы

4. Подготовка окружения на Ubuntu 24.04 LTS: Node.js 22 LTS и Swap

Подключитесь к вашему VDS по SSH. Обновите индекс пакетов и создайте файл подкачки (Swap), если объем оперативной памяти составляет менее 2 ГБ:

# Обновление системных пакетов
sudo apt update && sudo apt upgrade -y

# Создание и активация Swap-файла размером 2 ГБ
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

# Добавление Swap в автозагрузку
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

Установите актуальную стабильную версию Node.js 22 LTS через официальный репозиторий NodeSource и глобальный пакетный менеджер PM2:

# Установка зависимостей и добавление репозитория NodeSource (Node.js 22.x LTS)
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs build-essential git nginx

# Проверка установленных версий
node -v
npm -v

# Глобальная установка менеджера процессов PM2
sudo npm install -g pm2

5. Подготовка и сборка проекта Next.js: режим Standalone

В современных версиях Next.js (начиная с 13+ и актуальной 15+) встроен механизм сборки Standalone Output. Он анализирует зависимости проекта и создает минималистичный изолированный серверный бандл, которому не требуется вся многогигабайтная папка node_modules в продакшене.

В файле next.config.js (или next.config.mjs) вашего проекта активируйте директиву output: 'standalone':

// next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'standalone',
  reactStrictMode: true,
  poweredByHeader: false
};

export default nextConfig;

Создайте выделенную директорию для веб-проекта на сервере и установите безопасные права доступа (директории 755, владелец — ваш непривилегированный пользователь):

# Создаем каталог приложения
sudo mkdir -p /var/www/my-next-app
sudo chown -R $USER:$USER /var/www/my-next-app
cd /var/www/my-next-app

# Клонируем репозиторий или копируем исходные файлы
git clone https://github.com/your-username/my-next-app.git .

# Устанавливаем зависимости и выполняем сборку продакшен-бандла
npm ci
npm run build

При использовании output: 'standalone' билд создает специальный файл .next/standalone/server.js. Скопируйте статические файлы в директорию standalone:

# Копируем статику и публичные ассеты для автономного сервера
cp -r public .next/standalone/
cp -r .next/static .next/standalone/.next/

6. Конфигурация PM2: кластерный режим, лимиты памяти и автозагрузка

Критическое требование: архитектура Stateless для кластерного режима

Кластерный режим PM2 распределяет входящие запросы между воркерами по алгоритму Round-Robin без привязки сессий к конкретному процессу (session stickiness отсутствует). Это накладывает строгое архитектурное ограничение: приложение обязано быть Stateless.

  • Сессии пользователей: Категорически запрещено хранить сессии в переменных оперативной памяти (in-memory). Сессии должны храниться либо в защищенных подписанных JWT/Cookie на стороне клиента, либо в централизованном внешнем хранилище (Redis или базе данных).
  • WebSocket и события реального времени: Если приложение использует Socket.IO или Server-Sent Events, воркеры должны обмениваться событиями через брокер сообщений (например, с использованием @socket.io/redis-adapter). В противном случае сообщение, отправленное клиентом на воркер №1, не будет доставлено клиенту, подключенному к воркеру №2.

Вместо передачи параметров через длинные CLI-команды создайте декларативный файл конфигурации ecosystem.config.js в корневой папке проекта:

// /var/www/my-next-app/ecosystem.config.js
module.exports = {
  apps: [
    {
      name: 'next-app',
      script: '.next/standalone/server.js',
      instances: 'max', // Автоматическое распределение по всем ядрам vCPU (Cluster Mode)
      exec_mode: 'cluster',
      autorestart: true,
      watch: false,
      max_memory_restart: '350M', // Автоперезапуск инстанса при превышении 350 МБ RAM (защита от утечек)
      env: {
        NODE_ENV: 'production',
        PORT: 3000,
        HOSTNAME: '127.0.0.1' // Слушаем строго локальный интерфейс (защита от сканеров)
      }
    }
  ]
};

Запустите приложение под управлением PM2 и сохраните состояние в системную службу systemd:

# Запуск приложения по файлу конфигурации
pm2 start ecosystem.config.js

# Проверка статуса процессов и потребления ресурсов
pm2 status

# Настройка автозапуска PM2 при ребуте операционной системы
pm2 startup systemd

# Выполните команду, которую выведет терминал (sudo env PATH=$PATH... pm2 startup systemd -u ...)
# Затем зафиксируйте текущий список запущенных процессов
pm2 save

Полезные команды администратора для работы с PM2

Для просмотра логов в реальном времени используйте pm2 logs next-app. Для мониторинга дашборда CPU и памяти в терминале выполните pm2 monit. Для обновления без простоя пользователей используйте команду мягкой перезагрузки pm2 reload next-app.

7. Настройка Nginx Reverse Proxy: кэширование статики, WebSocket и сжатие

Создайте конфигурационный файл виртуального хоста Nginx /etc/nginx/sites-available/next-app.conf:

# /etc/nginx/sites-available/next-app.conf
# Определение пула бэкенд-серверов Node.js
upstream nextjs_backend {
    server 127.0.0.1:3000;
    keepalive 64;
}

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    # Сжатие gzip для ускорения отдачи текста, JS и CSS
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml image/svg+xml;

    # 1. Прямая отдача статических чанков Next.js с долгим кэшированием в браузере (1 год)
    location /_next/static/ {
        alias /var/www/my-next-app/.next/static/;
        expires 365d;
        access_log off;
        add_header Cache-Control "public, max-age=31536000, immutable";
    }

    # 2. Прямая отдача фавиконов, роботов и публичных картинок
    location /public/ {
        alias /var/www/my-next-app/public/;
        expires 30d;
        access_log off;
        add_header Cache-Control "public, max-age=2592000";
    }

    # 3. Проксирование всех динамических запросов на локальный порт Node.js
    location / {
        proxy_pass http://nextjs_backend;
        proxy_http_version 1.1;

        # Поддержка WebSocket (Hot Reload, Server-Sent Events)
        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;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Port $server_port;

        # Таймауты ожидания ответа от SSR
        proxy_read_timeout 60s;
        proxy_connect_timeout 60s;
    }
}

Почему заголовок «Cache-Control: immutable» безопасен и критически важен

При каждой сборке Next.js генерирует для JavaScript-чанков и CSS уникальные хэш-суммы содержимого в именах файлов (например, main-a1b2c3d4.js). Если вы обновите код сайта, имена изменившихся файлов автоматически изменятся. Заголовок Cache-Control: "public, max-age=31536000, immutable" указывает браузеру кэшировать эти ассеты ровно на 1 год и никогда не отправлять проверочные 304-запросы на сервер при обновлении страницы. Это ускоряет повторную загрузку сайта до 10–20 миллисекунд и снимает до 90% паразитной нагрузки с сервера.

Активируйте виртуальный хост и проверьте синтаксис Nginx:

# Создаем символическую ссылку
sudo ln -s /etc/nginx/sites-available/next-app.conf /etc/nginx/sites-enabled/

# Проверяем отсутствие синтаксических ошибок
sudo nginx -t

# Перезагружаем конфигурацию веб-сервера
sudo systemctl reload nginx

Сгенерируйте и сверьте готовые конфигурации веб-серверов в бесплатном интерактивном конструкторе SysKit:

8. Автоматический выпуск и продление SSL-сертификата Let's Encrypt

Установите Certbot и выпустите бесплатный TLS-сертификат с автоматическим перенаправлением всех посетителей с незащищенного HTTP на защищенный HTTPS:

# Установка Certbot и плагина для Nginx
sudo apt install -y certbot python3-certbot-nginx

# Выпуск сертификата с автоматическим редиректом
sudo certbot --nginx -d example.com -d www.example.com --redirect --non-interactive --agree-tos -m admin@example.com

Проверьте доступность портов и валидность выпущенного сертификата с помощью онлайн-утилит SysKit:

9. Сетевая безопасность: настройка фаервола UFW

По умолчанию порт Node.js приложения (3000) должен быть полностью закрыт от внешнего сканирования. Настройте межсетевой экран UFW, разрешив входящие подключения только для SSH, HTTP и HTTPS:

# Разрешаем стандартные входящие порты
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp comment 'SSH'
sudo ufw allow 80/tcp comment 'HTTP'
sudo ufw allow 443/tcp comment 'HTTPS'

# Активируем фаервол
sudo ufw --force enable
sudo ufw status verbose

10. Скрипт автоматического обновления проекта без простоя (Zero-Downtime Deploy)

Для быстрой выкатки свежих коммитов из Git без остановки сайта создайте в корне проекта исполняемый bash-скрипт deploy.sh:

#!/usr/bin/env bash
# /var/www/my-next-app/deploy.sh
set -e

echo "🚀 Начинаем процесс деплоя..."
cd /var/www/my-next-app

# 1. Загрузка свежих изменений из Git
git pull origin main

# 2. Установка зависимостей (только при изменении package-lock.json)
npm ci --prefer-offline

# 3. Сборка нового продакшен-бандла
npm run build

# 4. Копирование актуальной статики в standalone директорию
cp -r public .next/standalone/
cp -r .next/static .next/standalone/.next/

# 5. Мягкая перезагрузка кластера PM2 без потери входящих запросов
pm2 reload ecosystem.config.js --update-env

echo "✅ Проект успешно обновлен без простоя!"

Сделайте скрипт исполняемым: chmod +x deploy.sh. Теперь для выкатки обновлений достаточно выполнить ./deploy.sh на сервере или вызвать этот скрипт через SSH из пайплайна GitHub Actions.

Автоматизация деплоя через GitHub Actions

Узнайте, как настроить автоматический деплой сайта из репозитория GitHub на VDS по SSH с защитой секретов и zero-downtime развертыванием в нашей практической инструкции.

Открыть инструкцию по GitHub Actions для VPS

Вопросы и ответы (FAQ)

Популярные вопросы вебмастеров и сисадминов по данной теме

Почему приложение в кластере PM2 обязательно должно быть Stateless?
В кластерном режиме мастер-процесс PM2 распределяет входящие TCP-соединения между дочерними процессами без привязки клиента к конкретному воркеру. Если первый запрос пользователя обработал воркер А, то следующий клик может попасть на воркер Б. Если данные авторизации или корзины покупок хранятся в оперативной памяти процесса А, воркер Б ничего о них не узнает, и сессия пользователя оборвется. Поэтому сессии обязаны храниться во внешнем быстром хранилище (например, Redis) или передаваться в криптографически подписанных JWT-токенах.
В чем разница между командами pm2 restart и pm2 reload?
Команда pm2 restart принудительно убивает текущие процессы и запускает их заново, из-за чего клиенты в момент перезапуска могут получить ошибку 502 Bad Gateway. Команда pm2 reload выполняет мягкую поочередную перезагрузку воркеров кластера: сначала поднимается и инициализируется новый воркер, на него переключается трафик, и только затем корректно гасится старый, что обеспечивает 100% Zero-Downtime.
Зачем использовать режим Standalone в Next.js?
Обычная директория node_modules в проектах Next.js может весить от 500 МБ до 1.5 ГБ. Режим output: standalone с помощью алгоритмов трассировки дерева зависимостей собирает только те пакеты, которые реально используются в продакшене. Итоговый размер серверного каталога уменьшается в 5–10 раз, а скорость запуска многократно возрастает.
Почему не рекомендуется запускать Node.js напрямую на 80 или 443 порту?
Для привязки к привилегированным портам (ниже 1024) процессу требуются права суперпользователя root. Запуск интерпретатора Node.js от root — критическая уязвимость безопасности. Использование Nginx позволяет запускать Node.js от непривилегированного пользователя на локальном порту (3000), получая при этом аппаратную буферизацию, быстрое кэширование и надежное TLS-шифрование.

Читайте также в блоге SysKit

Смежные руководства и полезные технические статьи