Vercel делает деплой Next.js похожим на один git push. Это здорово — пока вам не нужен контроль над рантаймом, скучная машина или счёт, который не растёт вместе с трафиком клиента. Запустить Next.js на собственном VPS не сложно. Это рельса, которую вы прокладываете один раз, а потом git push вечно. Вот весь путь, готовый к копированию, с двумя подводными камнями, о которые действительно спотыкаются люди.

Что вы собираете

Стандартная продакшн-раскладка: ваше Next.js-приложение работает как долгоживущий Node-процесс на 127.0.0.1:3000, а Nginx стоит спереди, завершая TLS и проксируя в него. Вот и всё. Один сервер, одно приложение, ноль сложности с оркестрацией.

Internet → Nginx (:443, TLS) → Next.js server (:3000)

1. Возьмите сервер

Возьмите Ubuntu LTS VPS — 26.04 «Resolute Raccoon» — это текущий LTS, выпущен 23 апреля 2026.[1] Фиксируйте LTS, а не новейший промежуточный релиз.

  • 1–2 ГБ RAM достаточно для небольшого приложения.
  • Машина с 1 ГБ будет крутить Next.js, но уйдёт в своп во время сборки; 2 ГБ делают next build комфортным. Мы не публикуем цифру пикового потребления памяти сборкой, пока не замерили её на чистой машине — проверьте своё приложение через next build под /usr/bin/time -v.
  • Выбирайте любого провайдера (DigitalOcean, Linode, Vultr, Hetzner). Если вы в основном крутите одно приложение, бюджет важнее бренда.

2. Сначала захлопните дверь

Прежде чем трогать Node, защитите машину. Это шаг, который пропускают и о котором жалеют в 3 часа ночи.

# от root: создаём пользователя deploy и даём ему sudo
adduser deploy
usermod -aG sudo deploy

# только вход по ключу
mkdir -p /home/deploy/.ssh
cp ~/.ssh/authorized_keys /home/deploy/.ssh/authorized_keys
chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh && chmod 600 /home/deploy/.ssh/authorized_keys

# файрвол: только SSH, HTTP, HTTPS
ufw allow OpenSSH
ufw allow 'Nginx Full'
ufw enable

Затем отключите root-логин по SSH и вход по паролю в /etc/ssh/sshd_config:

PermitRootLogin no
PasswordAuthentication no

Перезапустите SSH (sudo systemctl restart ssh) и не закрывайте текущую сессию, пока не убедитесь, что новый вход работает.

3. Установите Node

Используйте nvm, чтобы фиксировать и переключать версии, а не зависеть от устаревшего Node дистрибутива.

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
# перезагрузите шелл, затем:
nvm install 24
nvm use 22

Установите текущий Active LTS-мажор — Node 24 на сентябрь 2026; Node 22 перешёл в Maintenance LTS 23 сентября 2026.[2] Неподдерживаемый мажор меняет флаги сборки и поведение некоторых модулей.

Вам нужен LTS Node — и стабильный. Как только вы деплоите, конкретная версия Node становится частью контракта вашего приложения — зафиксируйте её в engines в package.json:

"engines": { "node": ">=22" },

4. Доставьте код на сервер

Три варианта, в порядке роста автоматизации:

Самый простой первый деплой — scp/rsync:

rsync -avz --exclude node_modules --exclude .next ./ deploy@your-server:/home/deploy/apps/myapp/

Чище — git clone из вашего репозитория. Приложению нужна собственная директория:

sudo mkdir -p /home/deploy/apps
sudo chown deploy:deploy /home/deploy/apps
git clone https://github.com/you/myapp.git /home/deploy/apps/myapp
cd /home/deploy/apps/myapp

5. Соберите его (первый большой подводный камень)

Ставьте и собирайте на сервере:

npm ci
npm run build

Две вещи делают это надёжным:

  • Используйте npm ci, не npm install — он ставит из lockfile, воспроизводим и не даст зависимостям незаметно уехать в проде.
  • Задайте output: 'standalone' в next.config.js. Без этого вы потащите огромный node_modules и сервер, которому придётся резолвить всё на рантайме. С ним Next.js собирает самодостаточную папку /.next/standalone только с рантайм-кодом и нужными зависимостями.
// next.config.js
module.exports = {
  output: 'standalone',
};

Когда запускаете из standalone, стартуйте сервер изнутри /.next/standalone — там свой крошечный node_modules. Это второй подводный камень: если запустить pm2 start из корня проекта, вы запустите не тот бандл и наткнётесь на странные ошибки путей.

6. Запустите через PM2

PM2 держит процесс живым, перезапускает его при падении и стартует при перезагрузке. Забудьте про nohup и голый node &.

npm i -g pm2
# из корня проекта, или из .next/standalone, если нужен плотный бандл:
pm2 start server.js --name myapp

Лучше: объявите его в ecosystem.config.js в корне проекта, чтобы конфиг версионировался вместе с приложением:

module.exports = {
  apps: [{
    name: 'myapp',
    script: 'server.js',        // если standalone: '.next/standalone/server.js'
    cwd: '/home/deploy/apps/myapp',
    instances: 1,               // не ставьте 'max' бездумно на машине с 1 ГБ
    exec_mode: 'fork',
    env: { NODE_ENV: 'production', PORT: 3000 },
  }],
};

Затем:

pm2 start ecosystem.config.js
pm2 save
pm2 startup   # запустите напечатанную команду, чтобы включить старт при загрузке

Cluster-режим PM2 запускает несколько процессов и балансирует нагрузку между ними без правок кода.[3] На практике оставьте exec_mode: 'fork', пока вы не измерили, что один процесс не упирается в CPU; на небольшом VPS кластерный режим часто добавляет давление на память без видимой пользы для пользователя.

7. Nginx как входная дверь

Создайте /etc/nginx/sites-available/myapp:

server {
    listen 80;
    server_name app.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;
    }
}

Включите его и перезагрузите:

sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

proxy_http_version 1.1 плюс заголовки Upgrade/Connection — то, что позволяет WebSocket (и dev-режим HMR в Next.js) работать через прокси. Без них апгрейд соединения молча проваливается.

8. Бесплатный SSL через Let’s Encrypt

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d app.example.com
sudo certbot renew --dry-run

Плагин Certbot для nginx переписывает блок сервера, добавляя TLS и редирект с :80. proxy_pass оставьте как есть — меняются только слои TLS.

9. Подключите автодеплой

Вы в одном git push от установки уровня deploysmith. GitHub Actions workflow, который заходит по SSH и передеплоивает:

name: Deploy
on:
  push:
    branches: [main]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Deploy via SSH
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USER }}
          key: ${{ secrets.SSH_KEY }}
          script: |
            cd /home/deploy/apps/myapp
            git pull
            npm ci
            npm run build
            pm2 restart myapp

Если приложение собирается с output: 'standalone', Next.js пишет самодостаточную папку .next/standalone, которую вы запускаете через node .next/standalone/server.js;[4] сниппет выше предполагает server.js в корне проекта. Проверьте вашу точку входа, прежде чем фиксировать workflow.

Траблшутинг двух реально случающихся сбоев

502 Bad Gateway от Nginx. Прокси не может достучаться до 127.0.0.1:3000. Проверьте, что процесс реально слушает: pm2 list, затем curl http://127.0.0.1:3000. Обычно порт 3000 не занят, потому что сервер упал при загрузке.

Пути дают 404, но оболочка страницы загружается. Вы запустили не тот бандл. output: 'standalone' означает, что приложение должно стартовать из /.next/standalone, а папки .next/static и public должны быть скопированы рядом, иначе пути к ассетам не разрешатся. Если оставляете server.js на корневом уровне — откажитесь от standalone.

Итог «вот и всё»

VPS даёт тот же рантайм Next.js, что вы получили бы где угодно, плюс возможность задать версию Node, добавить кэш или запустить второй процесс. Путь короткий: защитите машину, поставьте Node, соберите с standalone, запустите через PM2, проксируйте через Nginx, TLS через Certbot. Единственные по-настоящему версионно-чувствительные детали — мажорная версия Node и настройки процесса PM2 — закрепите их, и остальное чисто механическое.

Источники

  1. Ubuntu 26.04 LTS «Resolute Raccoon» — анонс релиза Canonical (23 апр 2026). https://ubuntu.com/blog/2026/04/23/canonical-releases-ubuntu-26-04-lts-resolute-raccoon — проверено 2026-09-27
  2. Node.js Releases — график Active LTS / Maintenance. https://nodejs.org/en/about/previous-releases — проверено 2026-09-27
  3. PM2 — Cluster Mode (балансировка нагрузки между процессами). https://pm2.keymetrics.io/docs/usage/cluster-mode/ — проверено 2026-09-27
  4. Next.js — output: 'standalone' (справочник next.config.js). https://nextjs.org/docs/app/api-reference/config/next-config-js/output — проверено 2026-09-27