EvolCode
треки/Сервер и продакшн·05 / 08
9 мин чтения

Деплой на VPS: SSH, nginx, systemd

Vercel — отлично для большинства. Но иногда нужен свой сервер: тяжёлые backgound-задачи, специфический софт, бот который должен слушать webhook 24/7, большие файлы. Тогда — VPS. Hetzner / DigitalOcean / TimeWeb / Selectel — от $4 за 2GB RAM. Разберём минимальный полный цикл.

Покупка VPS и первый вход

Заказываешь VPS — выбираешь Ubuntu 24.04 (или Debian 12). Получаешь IP-адрес и root-пароль. Первое:

$ ssh root@IP

→ вводишь пароль. Сразу: создай отдельного юзера, не сиди под root: adduser deploy, потом

$ usermod -aG sudo deploy

Дальше залей свой ssh-ключ (

$ ssh-copy-id deploy@IP

) и отключи вход по паролю в

$ /etc/ssh/sshd_config

PasswordAuthentication no. Это базовая безопасность.

Что ставим

$ apt update && apt upgrade -y

обновить систему. Node.js:

$ curl -fsSL https://deb.nodesource.com/setup_22.x | bash && apt install nodejs

PM2 для запуска:

$ npm i -g pm2

nginx:

$ apt install nginx

Postgres (если нужен):

$ apt install postgresql

Git:

$ apt install git

Это весь стек минимального prod-сервера.

Деплой кода

Клонируешь репо:

$ git clone github.com/user/site.git /var/www/site
$ cd /var/www/site
$ npm ci

(важно: не install, а ci — детерминированно из package-lock).

$ cp .env.example .env

и заполнить реальными значениями.

$ npm run build

Запуск через PM2:

$ pm2 start npm --name site -- start

PM2 поднимет процесс, перезапустит при падении, переживёт reboot (

$ pm2 startup

+

$ pm2 save

).

nginx — публичный фасад

Твой Next.js крутится на :3000 локально. Снаружи нужно отдавать на :80 (HTTP) и :443 (HTTPS). Между ними — nginx как обратный прокси. Конфиг в

$ /etc/nginx/sites-available/site.conf
$ server { listen 80; server_name example.com; location / { proxy_pass http://localhost:3000; } }

Симлинк в sites-enabled,

$ nginx -t

(проверка),

$ systemctl reload nginx

Готово.

systemd vs PM2

Альтернатива PM2 — systemd-юнит. Создаёшь

$ /etc/systemd/system/mysite.service

с

$ ExecStart=/usr/bin/npm start
$ WorkingDirectory=/var/www/site

Restart=always.

$ systemctl enable mysite && systemctl start mysite

Сложнее настройка, но интегрировано в систему — те же journalctl-логи, единый интерфейс. Для одного сервиса — нормально. Для 5+ — PM2 удобнее.

Обновления

Простейший флоу: ssh на сервер →

$ cd /var/www/site && git pull && npm ci && npm run build && pm2 restart site

На один сайт — норм. Хочешь автоматизировать — GitHub Actions с SSH-ключом, который при push в main делает то же самое. Продвинуто — Docker + docker-compose, но это уже отдельная история.

примерbash
# === ПЕРВЫЙ ВХОД (с твоего ноута) ===
ssh root@123.45.67.89

# === БЕЗОПАСНОСТЬ ===
# Создай юзера
adduser deploy
usermod -aG sudo deploy

# Залей свой ssh-ключ (с ноута, не с сервера)
ssh-copy-id deploy@123.45.67.89

# Запрети логин по паролю
sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
systemctl reload ssh

# Настрой ufw (фаервол)
ufw allow ssh
ufw allow 80
ufw allow 443
ufw --force enable


# === УСТАНОВКА СТЕКА (теперь как deploy) ===
ssh deploy@123.45.67.89

sudo apt update && sudo apt upgrade -y
sudo apt install -y git nginx

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

# PM2
sudo npm i -g pm2

# Postgres (если нужен)
sudo apt install -y postgresql
sudo -u postgres createuser deploy --createdb
sudo -u postgres createdb mysite_db


# === ДЕПЛОЙ ПРОЕКТА ===
sudo mkdir -p /var/www/site
sudo chown deploy:deploy /var/www/site
git clone https://github.com/me/mysite.git /var/www/site
cd /var/www/site
npm ci
cp .env.example .env
nano .env                          # заполни DATABASE_URL и т.п.
npm run build

# Запуск через PM2
pm2 start npm --name site -- start
pm2 save
pm2 startup                        # выполни команду которую он выдаст


# === NGINX ===
sudo nano /etc/nginx/sites-available/site
# Содержимое:
server {
    listen 80;
    server_name example.com www.example.com;

    location / {
        proxy_pass http://localhost: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;
        proxy_cache_bypass $http_upgrade;
    }
}

sudo ln -s /etc/nginx/sites-available/site /etc/nginx/sites-enabled/
sudo nginx -t                      # проверка конфига
sudo systemctl reload nginx


# === ОБНОВЛЕНИЕ ===
ssh deploy@123.45.67.89
cd /var/www/site
git pull
npm ci
npm run build
pm2 restart site


# === ЛОГИ И МОНИТОРИНГ ===
pm2 logs site                      # логи приложения
pm2 monit                          # cpu/memory
sudo journalctl -u nginx -f        # логи nginx
df -h                              # сколько места на диске
free -h                            # сколько RAM свободно
htop                               # топ процессов

Полный цикл: от пустого VPS до работающего сайта

главное
  • 01VPS даёт полный контроль — нужен для тяжёлых задач, ботов, не-Vercel-стека
  • 02Создай юзера, отключи root-логин и вход по паролю — базовая безопасность
  • 03Стек: Node.js + PM2 + nginx + опционально Postgres
  • 04PM2 поднимает процесс, перезапускает при падении, переживает reboot
  • 05nginx работает как реверс-прокси: публичный :80/:443 → внутренний :3000
  • 06Обновление: git pull && npm ci && npm run build && pm2 restart
  • 07pm2 logs / journalctl / htop — главные команды для диагностики
проверь себя
01Зачем nginx если Next.js сам слушает порт 3000?
02Что делает PM2 что не делает обычный `npm start`?
03Первое что нужно сделать на свежем VPS после получения root-пароля?
следующий урок: Домены и DNS