Развернуть Next.js на своём сервере — вечер работы. Сделать так, чтобы это пережило первый инцидент, — задача другого масштаба, и разница между двумя состояниями обнаруживается обычно ночью.
Ниже — рабочая конфигурация с PM2 и nginx, а в конце то, чего в ней не хватает для спокойного сна.
Почему свой VPS, а не Vercel
Vercel — отличный выбор для большинства проектов. Но иногда нужен свой сервер:
- Данные должны храниться в России (152-ФЗ)
- Нужна интеграция с локальной инфраструктурой
- Высокий трафик — дешевле держать свой сервер
- Специфические требования к окружению
Что получите: NextJS на production с nginx reverse proxy, SSL от Let's Encrypt, автоперезапуск через PM2, CI/CD через GitHub Actions.
Шаг 1: Подготовка сервера
Арендуем VPS (Ubuntu 22.04 LTS). Минимум: 1 vCPU, 2 GB RAM.
# Подключаемся к серверу
ssh root@your-server-ip
# Обновляем систему
apt update && apt upgrade -y
# Создаём пользователя для деплоя
adduser deploy
usermod -aG sudo deploy
# Настраиваем SSH-ключи
su - deploy
mkdir -p ~/.ssh
chmod 700 ~/.ssh
# Добавляем свой публичный ключ в ~/.ssh/authorized_keysШаг 2: Установка Node.js
# Устанавливаем Node.js 20 LTS через nvm
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
source ~/.bashrc
nvm install 20
nvm use 20
nvm alias default 20
# Проверяем
node -v # v20.x.x
npm -v # 10.x.xШаг 3: Установка PM2
PM2 — менеджер процессов Node.js с автоперезапуском и мониторингом.
npm install -g pm2
# Автозапуск при перезагрузке сервера
pm2 startup systemd
# Выполните команду, которую выдаст pm2Шаг 4: Клонируем проект
cd /var/www
sudo mkdir -p myapp
sudo chown deploy:deploy myapp
cd myapp
git clone https://github.com/yourusername/your-nextjs-app.git .
npm ci
npm run buildШаг 5: Конфигурация PM2
Создаём конфигурационный файл:
// ecosystem.config.js
module.exports = {
apps: [
{
name: 'nextjs-app',
script: 'npm',
args: 'start',
cwd: '/var/www/myapp',
instances: 'max', // Используем все ядра CPU
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
PORT: 3000,
},
// Автоперезапуск при падении
autorestart: true,
watch: false,
max_memory_restart: '1G',
// Логи
error_file: '/var/log/pm2/nextjs-error.log',
out_file: '/var/log/pm2/nextjs-out.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
},
],
};Запускаем:
# Создаём папку для логов
sudo mkdir -p /var/log/pm2
sudo chown deploy:deploy /var/log/pm2
# Запускаем приложение
pm2 start ecosystem.config.js
# Сохраняем конфигурацию для автозапуска
pm2 save
# Проверяем статус
pm2 status
pm2 logs nextjs-appШаг 6: Настройка nginx
sudo apt install nginx -yСоздаём конфигурацию сайта:
# /etc/nginx/sites-available/myapp
upstream nextjs_upstream {
server 127.0.0.1:3000;
keepalive 64;
}
server {
listen 80;
server_name myapp.ru www.myapp.ru;
# Редирект на HTTPS (после настройки SSL)
# return 301 https://$server_name$request_uri;
location / {
proxy_pass http://nextjs_upstream;
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;
proxy_cache_bypass $http_upgrade;
# Таймауты
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# Статика NextJS — отдаём напрямую через nginx
location /_next/static {
alias /var/www/myapp/.next/static;
expires 1y;
access_log off;
add_header Cache-Control "public, immutable";
}
# Публичные файлы
location /public {
alias /var/www/myapp/public;
expires 1y;
access_log off;
}
# Favicon и robots
location = /favicon.ico {
alias /var/www/myapp/public/favicon.ico;
access_log off;
}
location = /robots.txt {
alias /var/www/myapp/public/robots.txt;
access_log off;
}
# Gzip
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
gzip_min_length 1000;
}Активируем:
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginxШаг 7: SSL-сертификат (Let's Encrypt)
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d myapp.ru -d www.myapp.ru
# Автопродление
sudo certbot renew --dry-runПосле получения сертификата nginx-конфиг автоматически обновится.
Шаг 8: Автодеплой через GitHub Actions
# .github/workflows/deploy.yml
name: Deploy to VPS
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Deploy to server
uses: appleboy/ssh-action@v1.0.0
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /var/www/myapp
git pull origin main
npm ci
npm run build
pm2 reload nextjs-appНастройка секретов
В Settings → Secrets → Actions добавьте:
SSH_HOST— IP сервераSSH_USER— пользователь (deploy)SSH_PRIVATE_KEY— приватный ключ
Безопасность: Никогда не коммитьте приватные ключи в репозиторий. Используйте GitHub Secrets или аналоги.
Шаг 9: Мониторинг
PM2 Dashboard
# Статус процессов
pm2 status
# Мониторинг в реальном времени
pm2 monit
# Логи
pm2 logs nextjs-app --lines 100Интеграция с PM2.io (опционально)
pm2 link <secret_key> <public_key>Полезные команды
# Перезапуск приложения
pm2 reload nextjs-app
# Полный рестарт
pm2 restart nextjs-app
# Остановка
pm2 stop nextjs-app
# Удаление из PM2
pm2 delete nextjs-app
# Очистка логов
pm2 flush
# Проверка nginx
sudo nginx -t
sudo systemctl reload nginx
# Просмотр логов nginx
sudo tail -f /var/log/nginx/error.logОптимизация производительности
1. Увеличиваем лимиты системы
# /etc/security/limits.conf
* soft nofile 65535
* hard nofile 655352. Настройка nginx для высокой нагрузки
# /etc/nginx/nginx.conf
worker_processes auto;
worker_rlimit_nofile 65535;
events {
worker_connections 4096;
use epoll;
multi_accept on;
}3. Включаем HTTP/2
listen 443 ssl http2;Чего здесь не хватает для продакшена
Описанная конфигурация рабочая, но её стоит дополнить — перечисляю в порядке важности.
Развёртывание не атомарно. git pull плюс npm run build плюс перезапуск означает, что во время сборки приложение либо работает на старом коде с новыми файлами, либо недоступно. Схема с отдельными директориями релизов и переключением симлинка решает и это, и откат: вернуться на предыдущую версию становится вопросом одной команды, а не повторного развёртывания.
Нет отката. Сейчас единственный способ вернуться назад — выкатить предыдущий коммит заново, то есть заново собрать. В момент инцидента это те самые минуты, которых не хватает.
Резервное копирование не настроено. Оно и не относится к развёртыванию напрямую, но именно на своём сервере это полностью ваша задача — включая проверку восстановления. Бэкап, который ни разу не разворачивали, бэкапом не является.
Нет мониторинга снаружи. PM2 перезапустит упавший процесс, но не заметит, что приложение отвечает ошибкой или что закончилось место на диске. Нужна внешняя проверка доступности с уведомлением — это бесплатно и настраивается за десять минут.
Автообновления безопасности. unattended-upgrades на Debian/Ubuntu — минимальная гигиена для сервера, смотрящего в интернет.
Firewall и SSH. Доступ по ключу, вход root по паролю отключён, открыты только нужные порты. Пункт очевидный ровно до момента, когда в логах обнаруживаются тысячи попыток подбора.
Итоги
Настроенная конфигурация закрывает базу: сборка, процесс-менеджер с автоперезапуском, обратный прокси с кэшированием статики, сертификат и автоматическое развёртывание из репозитория.
Что стоит держать в голове дальше:
PM2 — не единственный вариант. systemd даёт то же самое без дополнительной зависимости и лучше интегрирован с системой; Docker удобнее, когда окружений несколько или нужна воспроизводимость. PM2 хорош простотой и кластеризацией из коробки — этого достаточно для большинства проектов, но выбор стоит сделать осознанно.
Число процессов в кластере подбирайте по памяти, а не по числу ядер. Каждый экземпляр Next.js держит свою копию приложения в памяти, и на сервере с двумя гигабайтами instances: 'max' приводит к тому, что процессы начинают убиваться системой под нагрузкой — то есть ровно тогда, когда они нужнее всего.
Проверьте, что приложение переживает перезагрузку сервера. pm2 startup и pm2 save — две команды, отсутствие которых обнаруживается после первой же незапланированной перезагрузки.
Частые вопросы
Как запустить Next.js на VPS?
Установить Node LTS через nvm, склонировать проект, выполнить npm ci и npm run build, запустить через PM2 с конфигурацией в ecosystem.config.js и поставить перед приложением nginx обратным прокси на 127.0.0.1:3000. Статику из /_next/static отдавайте напрямую через nginx с годовым сроком кеширования — до Node эти запросы доходить не должны. Сертификат ставится утилитой certbot с плагином nginx.
Сколько процессов ставить в кластере PM2?
Подбирайте по памяти, а не по числу ядер. Каждый экземпляр Next.js держит свою копию приложения в памяти, поэтому на сервере с двумя гигабайтами значение instances в режиме max приводит к тому, что процессы начинают убиваться системой под нагрузкой — то есть ровно тогда, когда они нужнее всего. Ограничение max_memory_restart тоже стоит выставить осознанно.
PM2, systemd или Docker?
systemd даёт то же самое без дополнительной зависимости и лучше интегрирован с системой. Docker удобнее, когда окружений несколько или нужна воспроизводимость. PM2 хорош простотой и кластеризацией из коробки — этого достаточно для большинства проектов, но выбор стоит сделать осознанно, а не по инерции. И не забудьте команды pm2 startup и pm2 save: их отсутствие обнаруживается после первой же незапланированной перезагрузки сервера.
Почему развёртывание через git pull не атомарно?
Потому что связка git pull, npm ci, npm run build и перезапуск означает, что во время сборки приложение либо работает на старом коде с новыми файлами, либо недоступно. Схема с отдельными директориями релизов и переключением симлинка решает и это, и откат: вернуться на предыдущую версию становится вопросом одной команды, а не повторной сборки. Сейчас единственный способ откатиться — выкатить предыдущий коммит заново, и в момент инцидента это те самые минуты, которых не хватает.
Достаточно ли PM2 для мониторинга?
Нет. PM2 перезапустит упавший процесс, но не заметит, что приложение отвечает ошибкой, что закончилось место на диске или что сайт недоступен снаружи из-за конфигурации. Нужна внешняя проверка доступности с уведомлением — это бесплатно и настраивается за десять минут.
Что настроить на сервере кроме приложения?
Автообновления безопасности через unattended-upgrades — минимальная гигиена для сервера, смотрящего в интернет. Firewall с открытыми только нужными портами, вход по SSH-ключу и отключённый вход root по паролю: пункт очевидный ровно до момента, когда в логах обнаруживаются тысячи попыток подбора. И резервное копирование, которое на своём сервере целиком ваша задача, включая проверку восстановления: бэкап, который ни разу не разворачивали, бэкапом не является.