n8n на VPS: свой сервер автоматизации вместо n8n.cloud

n8n.cloud считает активные workflow и запуски и не достаёт до сервисов в вашей частной сети. Свой n8n на VPS снимает оба ограничения за цену тарифа в пару долларов. Разбираем docker compose, том с данными, три переменные окружения, которые молча ломают вебхуки, если задать их неправильно, вход через встроенную учётную запись владельца, периметр из nginx и ufw и то, что нужно бэкапить, чтобы не потерять workflow вместе с сервером.

n8n.cloud удобен ровно до тех пор, пока не упираетесь в тарифный лимит активных workflow или запусков в месяц, либо пока не понадобилось подключить сценарий напрямую к базе данных, которая не торчит в интернет. Развернуть n8n на VPS - способ снять оба ограничения за цену тарифа в пару долларов: workflow и запусков ровно столько, сколько выдержит сервер, а к внутренним сервисам можно ходить напрямую. Ниже - развёртывание через docker compose, том для данных, три переменные окружения, которые молча ломают вебхуки при первой же ошибке, встроенная учётная запись владельца вместо давно убранного basic auth, периметр из nginx и ufw и то, что реально нужно бэкапить.

Коротко. n8n ставится одним compose-файлом из образа n8nio/n8n, слушает порт 5678 и хранит всё - workflow, учётные данные подключений, историю запусков и ключ шифрования - в SQLite-файле внутри каталога /home/node/.n8n, который обязательно выносится томом на хост. Ресурсов инстансу нужно немного: по документации n8n простаивающий процесс занимает около 100 МБ, а рабочий диапазон для несложных сценариев - 320 МБ - 2 ГБ, это не LLM-стек. За обратным прокси адрес, который n8n собирает сам из N8N_HOST/N8N_PROTOCOL/N8N_PORT, не совпадает с реальным публичным URL - вебхуки регистрируются на внешних сервисах именно по нему, и если он неверный, интеграции молча ломаются. Правильный адрес задаётся отдельно через N8N_WEBHOOK_URL. Логин через переменные окружения (N8N_BASIC_AUTH_ACTIVE) в текущих версиях не работает - авторизация идёт через встроенную учётную запись владельца. Порт наружу не публикуйте: 127.0.0.1:5678:5678 в compose, nginx с доменом и HTTPS сверху, ufw на 80 и 443.

Зачем n8n на своём VPS, если есть n8n.cloud

n8n - инструмент визуальной автоматизации: вы соединяете узлы («ноды») в схему - пришёл вебхук, дёрнули внешний API, записали результат в таблицу - без своего бэкенда под каждый такой сценарий. n8n.cloud - управляемая версия этого же продукта, и для разовой задачи она разумный выбор: не нужно поднимать сервер, обновления происходят сами. Но у управляемого тарифа есть два системных ограничения, которые упираются в стену рано или поздно.

Первое - лимиты плана: число активных workflow, запусков в месяц, участников команды. Второе, более неприятное для разработчика или агентства - облачный n8n физически не может достучаться до сервиса, который не выставлен в открытый интернет. Внутренняя PostgreSQL на VPS клиента, API во внутренней сети, локальный Redis - всё это либо приходится временно открывать наружу ради интеграции, либо тащить через промежуточный туннель. На собственном VPS n8n и целевой сервис могут жить в одной приватной сети без единого открытого порта.

Своя причина не менее практичная: лицензия. n8n распространяется по Sustainable Use License - разрешает бесплатно ставить, менять и использовать продукт для своих внутренних задач, запрещает перепродавать его как услугу другим. Именно поэтому self-hosted n8n на VPS - легальный и бесплатный вариант для своей команды или клиентских проектов, а не серая зона.

Что важно

n8n.cloud

n8n на своём VPS

Лимит активных workflow и запусков

По тарифному плану

Ограничен только ресурсами сервера

Доступ к внутренним сервисам без открытого порта

Нет, нужен публичный адрес

Да, если сервисы в одной сети или на одном хосте

Обновление версии и патчи безопасности

Делает n8n

Делаете вы: docker compose pull

Бэкап данных

Отвечает провайдер

Отвечаете вы, ниже - как

Ежемесячная стоимость

От платного тарифа за место в команде

Цена тарифа VPS, независимо от числа workflow

Сколько ресурсов реально нужно n8n на VPS

n8n - не языковая модель и не векторная база: он не держит гигабайты весов в памяти, а выполняет workflow последовательно, шаг за шагом. По документации проекта простаивающий инстанс занимает около 100 МБ памяти, а типичный диапазон для несложных сценариев - от 320 МБ до 2 ГБ, в зависимости от числа воркфлоу, объёма данных, которые проходят через ноды, и от того, сколько запусков идёт параллельно. Это ориентир, а не гарантия: обработка большого файла внутри одной ноды - например, парсинг многостраничного PDF или загрузка тяжёлого CSV в память целиком - может дать разовый скачок памяти сильно выше среднего.

Сценарий

Ориентир по памяти на весь сервер

Личные автоматизации, несколько workflow, редкие срабатывания

1-2 ГБ

Небольшая команда, регулярные вебхуки и интеграции с API

2-4 ГБ

Много параллельных запусков, тяжёлая обработка данных в нодах, вынесенная PostgreSQL

4-8 ГБ и выше

Диск считайте так же скромно: сам образ и его слои - меньше гигабайта, дальше растёт файл SQLite вместе с историей запусков. n8n по умолчанию хранит выполненные запуски бессрочно, и на активном инстансе эта история годами способна разрастись до заметного объёма - если диск маленький, настройте автоочистку старых executions в настройках инстанса, это отдельная переменная окружения и в объём этой статьи не входит.

Шаг 1. Docker и каталог для данных

Ставим Docker из репозитория самого Docker, а не из репозитория Ubuntu: пакет docker.io из дистрибутива отстаёт по версиям, и docker compose в нём отдельная история. Команды ниже - официальный способ, они одинаковы для Ubuntu 22.04, 24.04 и 26.04.

sudo apt-get update

sudo apt-get install -y ca-certificates curl

sudo install -m 0755 -d /etc/apt/keyrings

sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc

sudo chmod a+r /etc/apt/keyrings/docker.asc

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt-get update

sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

  • $(. /etc/os-release && echo "$VERSION_CODENAME") подставляет кодовое имя вашей версии Ubuntu, строку не нужно править руками.
  • docker-compose-plugin - это docker compose в два слова. Старый docker-compose через дефис - отдельный устаревший инструмент.

Проверка: docker compose version должен вывести строку вида Docker Compose version v2.x.x. Если вместо этого docker: 'compose' is not a docker command - плагин не установился, повторите последнюю команду.

Теперь каталог для данных. n8n хранит workflow, учётные данные для подключений и историю запусков в файле SQLite внутри каталога /home/node/.n8n - это фиксированный путь внутри образа, менять его нет смысла. Каталог обязательно выносится томом на хост, иначе любой docker compose down сотрёт все workflow вместе с контейнером.

sudo mkdir -p /opt/n8n/data

cd /opt/n8n

sudo chown -R 1000:1000 /opt/n8n/data

  • data - сюда примонтируется /home/node/.n8n из контейнера, вся персистентность именно здесь.
  • chown 1000:1000 - процесс внутри образа работает от пользователя node, а не от root, и без прав на запись контейнер не сможет создать файл базы данных при первом старте.

Шаг 2. compose-файл: том, порт и переменные, которые ломают вебхуки

Кладём /opt/n8n/docker-compose.yml:

services:

n8n:

image: n8nio/n8n:latest

container_name: n8n

restart: unless-stopped

ports:

- "127.0.0.1:5678:5678"

environment:

- N8N_HOST=n8n.example.com

- N8N_PROTOCOL=https

- N8N_PORT=5678

- N8N_WEBHOOK_URL=https://n8n.example.com/

- N8N_PROXY_HOPS=1

- N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true

- N8N_ENCRYPTION_KEY=замените-на-случайную-строку-от-32-символов

- GENERIC_TIMEZONE=Europe/Amsterdam

- TZ=Europe/Amsterdam

volumes:

- ./data:/home/node/.n8n

  • 127.0.0.1:5678:5678 - порт публикуется только на loopback, снаружи его не существует. Наружу будет смотреть nginx, разбор - в разделе про периметр.
  • N8N_HOST, N8N_PROTOCOL, N8N_PORT - из них n8n сам собирает адрес, по которому он себя видит. За обратным прокси этот адрес не совпадает с реальным публичным: внутри контейнер слушает 5678 по http, снаружи должен быть домен по https на 443.
  • N8N_WEBHOOK_URL - публичный адрес, который n8n показывает в интерфейсе редактора и подставляет во все внешние интеграции (например, при регистрации вебхука в Telegram или Stripe). Это актуальное имя переменной: старая WEBHOOK_URL считается устаревшей начиная с версии 2.35.0. Не задать её или задать с ошибкой в домене - вебхук создастся, но внешний сервис будет стучаться по неверному адресу, и это не выдаст явной ошибки в момент создания workflow.
  • N8N_PROXY_HOPS=1 - говорит n8n, что перед ним стоит один обратный прокси, и заголовку X-Forwarded-Proto от него можно доверять. Без этой переменной n8n может решить, что запрос пришёл по обычному http, даже если снаружи всё было по https - и тогда упрётся в защиту куки, о которой ниже.
  • N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true - требует, чтобы файл с настройками и ключом шифрования внутри тома был читаем только владельцем. Это из примера в официальной документации, назначение - не оставлять секреты с правами на чтение для всех.
  • N8N_ENCRYPTION_KEY - без этой переменной n8n сам сгенерирует ключ и сохранит его в тот же том при первом запуске. Указать свой ключ явно стоит ради бэкапа: если вы когда-нибудь захотите восстановить только дамп базы данных на новом сервере без переноса всего тома целиком, без этого же самого ключа зашифрованные учётные данные подключений останутся нечитаемыми. Генерируется командой openssl rand -hex 32.
  • GENERIC_TIMEZONE и TZ - часовой пояс, по которому будут срабатывать расписания (cron-триггеры) внутри workflow. Не указать - триггеры сработают по UTC, что для расписаний вроде «каждый день в 9 утра» обычно не то, что вы имели в виду.

Поднимаем:

cd /opt/n8n

sudo docker compose up -d

sudo docker compose ps

В выводе ps статус должен быть running, а в колонке портов - 127.0.0.1:5678->5678/tcp. Если статус restarting - смотрите sudo docker compose logs -n 50 n8n: на этом этапе почти всегда виноваты права на том с данными.

Шаг 3. Учётная запись владельца вместо basic auth

Если вы уже разворачивали сервисы вроде WordPress или старых версий других open-source проектов, привычка - искать переменные вида *_BASIC_AUTH_USER и задавать логин с паролем прямо в compose-файле. У n8n такие переменные (N8N_BASIC_AUTH_ACTIVE, N8N_BASIC_AUTH_USER, N8N_BASIC_AUTH_PASSWORD) существовали, но начиная с версии 1.0 базовая авторизация из продукта убрана, и эти переменные больше ни на что не влияют - оставить их в файле не вредно, но и не действует.

Вместо этого при первом открытии интерфейса n8n сам показывает форму создания учётной записи владельца - почта и пароль. Это не опциональный шаг, который можно пропустить: без него в интерфейс не попасть вообще. Дальше из-под этой учётной записи в разделе управления пользователями можно пригласить участников с более узкими ролями - разница по сути та же, что в статье про AnythingLLM: одна общая точка входа против полноценных ролей для команды.

Добираемся до интерфейса для первичной настройки, не открывая ничего наружу, - через SSH-туннель:

ssh -L 5678:127.0.0.1:5678 root@ВАШ_IP

Пока сессия открыта, http://127.0.0.1:5678 в вашем браузере - это порт на сервере. Создайте учётную запись владельца сейчас, до того как появится домен и публичный доступ, а не после.

Шаг 4. nginx и ufw перед n8n

Как и с любым другим сервисом в Docker, здесь работает то же правило периметра, которое подробно разобрано в статье про AnythingLLM на VPS: Docker при публикации порта пишет собственные правила в iptables раньше тех, куда пишет ufw, поэтому запрет вроде ufw deny 5678 здесь ничего не даёт, а ufw status при этом выглядит совершенно спокойно. Строка 127.0.0.1:5678:5678 в compose-файле выше уже решает эту проблему - порт просто не существует снаружи, и весь внешний трафик обязан идти через nginx.

Серверный блок - обычный обратный прокси с заголовками, которые нужны n8n для вебхуков и живых обновлений в редакторе:

server {

listen 80;

server_name n8n.example.com;

client_max_body_size 50M;

location / {

proxy_pass http://127.0.0.1:5678;

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";

}

}

  • X-Forwarded-Proto $scheme - именно этот заголовок n8n читает благодаря N8N_PROXY_HOPS=1, чтобы понять, что снаружи был https, даже если сам он отвечает по обычному http.
  • Upgrade и Connection - редактор n8n держит соединение для живых обновлений интерфейса, без этих заголовков часть UI будет работать рывками.
  • client_max_body_size 50M - лимит на тело запроса к вебхукам. Умолчание nginx - 1 МБ, и любой вебхук с файлом или крупным JSON-payload’ом упрётся в 413. 50 МБ - разумный старт, поднимайте под свою нагрузку.

Дальше по стандартной схеме: sudo nginx -t, и только если проверка прошла - sudo systemctl reload nginx. Сертификат TLS ставится отдельным шагом после того, как HTTP уже отвечает - обычно через certbot:

sudo apt install -y certbot python3-certbot-nginx

sudo certbot --nginx -d n8n.example.com

После установки сертификата N8N_PROTOCOL=https и N8N_WEBHOOK_URL с https:// в compose-файле должны совпадать с тем, что реально отдаёт сервер - иначе браузер начнёт жаловаться на «незащищённую куку» при входе по паролю, потому что n8n ждёт https, а получает http, или наоборот. Полный разбор конфига, минимального набора правил ufw и того, почему reload, а не restart, - в статье про nginx и ufw перед вашим приложением.

Шаг 5. Что бэкапить, чтобы не потерять workflow

Полноценный бэкап n8n - это каталог /home/node/.n8n целиком (или дамп внешней базы, если вы подключили PostgreSQL вместо SQLite), а не только сами workflow. Внутри, кроме файлов с логикой автоматизаций, лежат зашифрованные учётные данные для подключений, история запусков и ключ шифрования - тот самый, который вы либо задали переменной N8N_ENCRYPTION_KEY, либо n8n сгенерировал сам при первом старте.

Есть отдельный способ - CLI-экспорт, он удобен, чтобы перенести конкретные workflow между инстансами, но не заменяет полный бэкап:

sudo docker compose exec n8n n8n export:workflow --backup --output=/home/node/.n8n/backup/workflows/

sudo docker compose exec n8n n8n export:credentials --backup --output=/home/node/.n8n/backup/credentials/

  • --backup - сокращение для «экспортировать всё, красиво отформатировать, разложить по отдельным файлам».
  • Каталог для экспорта здесь намеренно указан внутри уже смонтированного тома /home/node/.n8n - если вывести файлы в любой другой путь внутри контейнера, они пропадут при следующем пересоздании контейнера, потому что тот путь никуда не смонтирован.
  • Такой экспорт не включает пользователей, историю запусков, переменные окружения инстанса и сам ключ шифрования - по документации n8n этого недостаточно для восстановления инстанса целиком, только для переноса конкретных workflow.

Для реального плана резервного копирования сервера - тома целиком плюс шифрование, хранение вне сервера, автоматический запуск по расписанию - используйте общий метод из статьи как бэкапить свой сервер на HIP: в случае n8n источником для restic или дампа становится каталог /opt/n8n/data целиком, а не отдельные экспортированные файлы.

Если не заработало

  • Контейнер перезапускается по кругу. Первым делом sudo docker compose logs -n 50 n8n. Ошибки доступа к файлам почти всегда означают неверные права на том - повторите chown -R 1000:1000 /opt/n8n/data.
  • Вебхук создаётся, но внешний сервис на него не приходит. Откройте workflow и посмотрите, какой адрес n8n подставил в узел вебхука - если там не ваш домен из N8N_WEBHOOK_URL, а что-то вроде адреса контейнера, значит переменная не подхватилась при последнем перезапуске. Проверьте, что она реально попала в контейнер: docker compose exec n8n env | grep WEBHOOK.
  • Браузер жалуется на незащищённую куку при входе. Значит n8n думает, что общается по http, хотя снаружи всё идёт по https. Проверьте по порядку: сертификат реально стоит и nginx слушает 443; в compose есть N8N_PROTOCOL=https; в nginx есть строка proxy_set_header X-Forwarded-Proto $scheme; в compose есть N8N_PROXY_HOPS=1.
  • После обновления образа исчезли workflow. Значит том не был смонтирован правильно - проверьте, что в docker compose config путь ./data:/home/node/.n8n указывает туда, где реально лежат файлы, а не в пустой каталог, случайно созданный заново.
  • Диск заканчивается со временем без явной причины. Разрастается история выполненных workflow в SQLite. Настройте автоочистку старых executions в настройках инстанса или регулярно смотрите на размер файла базы данных.

FAQ

Чем n8n на своём сервере отличается от n8n.cloud?

n8n.cloud - управляемый тариф с лимитами на активные workflow, число запусков и участников, и он не может подключиться к сервису в вашей частной сети, только к тому, что доступно из интернета. На своём VPS оба ограничения снимаются: workflow ровно столько, сколько выдержит сервер, а к внутренней базе или API можно ходить напрямую. Взамен обновление образа, бэкап и мониторинг диска и памяти становятся вашей задачей.

Сколько оперативной памяти нужно n8n на VPS?

По документации n8n простаивающий инстанс занимает около 100 МБ, а типичный диапазон для несложных сценариев - 320 МБ - 2 ГБ. Для личных и небольших командных автоматизаций достаточно тарифа на 1-2 ГБ. Тяжёлая обработка данных внутри нод, много параллельных запусков или вынесенная PostgreSQL требуют памяти заметно больше.

Как правильно настроить N8N_HOST и WEBHOOK_URL?

N8N_PROTOCOL, N8N_HOST и N8N_PORT собирают адрес, по которому n8n видит сам себя, но за обратным прокси он не совпадает с реальным публичным URL. Внешний адрес для вебхуков задаётся отдельно через N8N_WEBHOOK_URL (актуальное имя переменной, WEBHOOK_URL устарела с версии 2.35.0) - туда пишется полный публичный адрес с https. Рядом обязательно ставится N8N_PROXY_HOPS=1, иначе n8n неправильно определяет протокол запроса по заголовкам от прокси.

Почему в n8n не работает вход по логину и паролю из переменных окружения?

Переменные N8N_BASIC_AUTH_ACTIVE, N8N_BASIC_AUTH_USER и N8N_BASIC_AUTH_PASSWORD существовали в старых версиях, но начиная с n8n 1.0 базовая авторизация убрана из продукта, и эти переменные ничего не делают. При первом заходе в интерфейс n8n сам просит создать учётную запись владельца с почтой и паролем, а дальше в неё добавляются пользователи с ролями через встроенное управление пользователями.

Где n8n хранит workflow и как их бэкапить?

Всё - workflow, зашифрованные учётные данные подключений, история запусков и ключ шифрования - лежит в SQLite-файле внутри каталога /home/node/.n8n, который обязательно выносится томом на хост. Полноценный бэкап - этот каталог целиком (или внешняя база, если вы используете PostgreSQL), потому что CLI-экспорт workflow и credentials не включает пользователей, историю запусков и сам ключ шифрования, без которого восстановленные учётные данные подключений нечитаемы.

Нужен ли n8n отдельный сервер под базу данных?

Нет, для старта не нужен. По умолчанию n8n использует встроенную SQLite прямо в своём томе с данными, и для одного пользователя или небольшой команды этого достаточно. Отдельная PostgreSQL нужна, когда параллельно работают несколько воркеров в режиме очереди или число одновременных запусков становится большим и SQLite упирается в запись.

Коротко

  • n8n на VPS снимает два системных ограничения n8n.cloud: лимиты тарифа на workflow и запуски, и невозможность достучаться до сервисов в вашей частной сети без открытого порта.
  • Ресурсы скромные: по документации n8n простаивающий инстанс - около 100 МБ, рабочий диапазон - 320 МБ - 2 ГБ. Это не LLM-стек, тариф на 1-2 ГБ подходит для старта.
  • Вся персистентность - каталог /home/node/.n8n, вынесенный томом на хост. Не вынесли - потеряли всё при первом же docker compose down.
  • N8N_HOST/N8N_PROTOCOL/N8N_PORT - для внутреннего использования n8n. Публичный адрес для вебхуков задаётся отдельно через N8N_WEBHOOK_URL, и без N8N_PROXY_HOPS=1 за прокси эта связка ломается молча.
  • Логин через переменные окружения (basic auth) в n8n больше не работает. Доступ - через встроенную учётную запись владельца, созданную при первом входе, и роли пользователей поверх неё.
  • CLI-экспорт workflow - не бэкап инстанса целиком. Для восстановления нужен весь том с данными или дамп внешней БД плюс ключ шифрования.

Что дальше

n8n - ещё один сервис в Docker на том же VPS, и вокруг него работают те же правила периметра и эксплуатации, что и для остальной self-hosted инфраструктуры. По порядку:

PUBLISHED
AUTHOR
HIP-HOSTING
LANGUAGES
EN · RU