n8n на VPS: установка через Docker Compose, HTTPS и автозапуск
n8n на своём VPS поднимается через Docker Compose за один вечер: два контейнера (сам n8n и обратный прокси Caddy, который сам выпускает HTTPS-сертификат), три небольших файла и одна команда запуска. Ниже — готовые файлы, объяснение каждой переменной, первый сценарий с вебхуком, резервная копия и обновление. Все выводы сняты на тестовом сервере с n8n версии 2.45.7.
Что такое n8n и зачем ставить его самому
n8n — конструктор автоматизаций. Вы собираете схему из блоков: пришла заявка с сайта, добавить строку в таблицу, отправить сообщение в мессенджер, через час проверить ответ. Каждый блок называется узлом, а вся схема — сценарием (в интерфейсе workflow). Готовых узлов для популярных сервисов сотни, а к любому сервису с HTTP API можно обратиться напрямую.
У n8n есть платная облачная версия, но на собственном сервере вы платите только за VPS и сами решаете, сколько сценариев запускать. Вторая причина — данные: токены и пароли от ваших сервисов лежат у вас, а не у третьих лиц. Взамен вы сами отвечаете за обновления, резервные копии и защиту. Обо всём этом дальше.
Что подготовить
- VPS с Docker. Установка описана в отдельной статье. По памяти n8n скромен: в простое он занял 380 МБ, Caddy — 15 МБ. Но сценарии с большими файлами или сотнями запусков в минуту потребуют больше, поэтому берите сервер от 2 ГБ, например конфигурацию с 2 vCore и 2 ГБ из линейки VPS UFO.Hosting. Образ n8n тяжёлый, 1,81 ГБ, так что посмотрите и на запас по диску.
- Домен или поддомен, допустим
n8n.example.com. Создайте для него A-запись (DNS-запись, которая связывает имя с IPv4-адресом) на IP вашего сервера. Как это сделать — в статье «Как прописать DNS для сервера». Запись должна заработать до запуска: Caddy сразу после старта пойдёт за сертификатом, и если домен ещё не указывает на сервер, получит отказ. Проверить можно командойgetent hosts n8n.example.com: она должна показать ваш IP. - Открытые порты 80 и 443. Через них Let’s Encrypt (бесплатный центр сертификации) проверяет домен, они же нужны посетителям. Если у вас включён
ufw, откройте их:sudo ufw allow 80/tcpиsudo ufw allow 443/tcp. Для опубликованных портов Docker это формальность, он их откроет и сам, но правила пригодятся, если когда-нибудь схема изменится.
Для пробы без домена подойдёт адрес вида n8n.203-0-113-10.sslip.io, где 203-0-113-10 — ваш IP с дефисами вместо точек: служба sslip.io сама отдаёт такое имя на нужный адрес. Сертификат для него обычно выпускается, хотя на общем домене возможны лимиты центра сертификации. Для постоянной работы заводите свой домен.
Три файла проекта
Создайте каталог и перейдите в него:
mkdir ~/n8n && cd ~/n8n
Файл .env. Это набор переменных, которые Compose подставляет в compose.yaml: так меняющиеся значения лежат отдельно от остальной настройки. Создадим его сразу со случайным ключом шифрования:
cat > .env <<EOF
DOMAIN=n8n.example.com
GENERIC_TIMEZONE=Europe/Berlin
N8N_ENCRYPTION_KEY=$(openssl rand -hex 32)
EOF
chmod 600 .env
Замените n8n.example.com на ваш домен. По строкам:
DOMAIN— адрес, по которому будет открываться n8n.GENERIC_TIMEZONE— часовой пояс для сценариев по расписанию. Если его не задать, n8n считает время по Нью-Йорку, и «каждый день в 9:00» сработает не тогда, когда вы ждёте. Названия поясов берут из базы tz database, напримерEurope/BerlinилиAsia/Almaty.N8N_ENCRYPTION_KEY— ключ, которым n8n шифрует сохранённые доступы к сервисам: пароли, токены, API-ключи. Командаopenssl rand -hex 32создаёт случайную строку из 64 символов. Если ключ потерять, сохранённые доступы станут нечитаемыми и вводить их придётся заново. Поэтому держите копию файла.envвне сервера, например в менеджере паролей.
Команда chmod 600 оставляет читать и менять файл только его владельцу. Ключ можно и не задавать: тогда n8n создаст его сам при первом запуске и сохранит в своих данных. Мы задаём его заранее, чтобы он был у вас под рукой. Заданный ключ n8n в любом случае записывает и в свой каталог данных, мы проверили, что значения совпадают, так что копия тома тоже его содержит.
Файл Caddyfile. Это настройка обратного прокси: программы, которая принимает запросы из интернета на портах 80 и 443 и передаёт их контейнеру. Выпуск и продление сертификата Caddy берёт на себя. Создайте файл (nano Caddyfile) и вставьте:
{$DOMAIN} {
reverse_proxy n8n:5678
}
Это весь файл. Адрес {$DOMAIN} Caddy берёт из переменной окружения, которую передаёт compose.yaml, а n8n:5678 — это имя сервиса n8n и порт, на котором он слушает внутри Docker.
Файл compose.yaml (nano compose.yaml) описывает оба контейнера:
services:
caddy:
image: caddy:2
restart: unless-stopped
ports:
- "80:80"
- "443:443"
environment:
- DOMAIN
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
depends_on:
- n8n
n8n:
image: docker.n8n.io/n8nio/n8n:2.45.7
restart: unless-stopped
environment:
- N8N_HOST=${DOMAIN}
- N8N_PROTOCOL=https
- N8N_WEBHOOK_URL=https://${DOMAIN}/
- N8N_PROXY_HOPS=1
- N8N_ENCRYPTION_KEY
- GENERIC_TIMEZONE
- TZ=${GENERIC_TIMEZONE}
volumes:
- n8n_data:/home/node/.n8n
volumes:
caddy_data:
n8n_data:
Что в этом файле не очевидно:
- Порты 80 и 443 публикует только Caddy. У n8n порт 5678 наружу не открыт: до него можно дойти только через прокси. Почему это важно, написано в статье про Docker: Docker обходит правила
ufw. - Строка вроде
- DOMAINбез значения берёт его из.env. Значения в строках с=заданы прямо здесь. N8N_HOSTиN8N_PROTOCOLсообщают n8n, по какому адресу его открывают.TZзадаёт часовой пояс самой системы внутри контейнера, в частности для журналов.N8N_WEBHOOK_URL— публичный адрес для вебхуков. Без него, как сказано в документации n8n, адрес вебхука собирается из протокола, имени хоста и порта 5678, то есть показывает внутренний адрес, а не тот, по которому вас видят снаружи. В старых инструкциях эта переменная называетсяWEBHOOK_URL.N8N_PROXY_HOPS=1сообщает, что перед n8n стоит один прокси и его заголовкам можно доверять, чтобы n8n видел настоящий адрес посетителя, а не адрес Caddy.- Данные n8n лежат в томе
n8n_data, сертификаты Caddy — вcaddy_data. Тома переживают пересоздание контейнеров. Сертификаты хранить важно: без них Caddy при каждом пересоздании просил бы новые, а у Let’s Encrypt есть лимиты на выпуск. - Версия образа закреплена (
2.45.7). Без тега вы получали бы последнюю версию при каждом обновлении, в том числе новую крупную, и не заметили бы этого. Новые версии смотрите в списке изменений n8n.
Проверим файл:
docker compose config --quiet && echo OK
OK
Запуск
Запускаем оба контейнера в фоне:
docker compose up -d
Образ n8n весит 1,81 ГБ, поэтому скачивание займёт заметное время. Когда команда закончится, посмотрим состояние и журнал:
docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
n8n-caddy-1 caddy:2 "caddy run --config …" caddy 33 minutes ago Up 4 minutes 0.0.0.0:80->80/tcp, [::]:80->80/tcp, 0.0.0.0:443->443/tcp, [::]:443->443/tcp, 443/udp, 2019/tcp
n8n-n8n-1 docker.n8n.io/n8nio/n8n "tini -- /docker-ent…" n8n 33 minutes ago Up 4 minutes 5678/tcp
docker compose logs n8n
...
Version: 2.45.7
...
Editor is now accessible via:
https://n8n.example.com
Запись 5678/tcp без стрелки значит, что порт наружу не опубликован. В журнале в первые минуты будут и предупреждения про устаревающие переменные окружения (N8N_RUNNERS_MODE, N8N_UNVERIFIED_PACKAGES_ENABLED и другие). Это сообщения о будущей смене значений по умолчанию, ошибками они не являются.
Сертификат смотрим в журнале Caddy:
docker compose logs caddy | grep "certificate obtained"
... "msg":"certificate obtained successfully","identifier":"n8n.example.com","issuer":"acme-v02.api.letsencrypt.org-directory"}
Выпуск занял у нас несколько секунд. Если вместо этого появились строки с error про проверку домена, значит, домен ещё не указывает на сервер или закрыты порты 80 и 443. Исправьте и перезапустите Caddy командой docker compose restart caddy.
Первый вход
Откройте адрес в браузере. n8n покажет форму создания владельца:

Заполните её сразу. Пока владельца нет, форма открыта любому, кто знает адрес, и первый, кто её заполнит, получит полный контроль над сервисом. Новые домены боты находят за минуты: они следят за публичными журналами выпущенных сертификатов (подробнее в статье про Nextcloud). n8n требует не менее 8 символов, цифру и заглавную букву, но придумайте пароль подлиннее.
Анкету о вашей компании, окно подключения модели для встроенного помощника и предложение бесплатной лицензии на платные функции по электронной почте можно пропустить: для работы они не нужны. После этого откроется главный экран:

Первый сценарий: приём заявки по вебхуку
Вебхук — это адрес, на который другая программа отправляет запрос, чтобы запустить ваш сценарий. Например, форма на сайте. Соберём самый простой сценарий из двух узлов: он принимает запрос и отвечает присланными данными.
Проще всего вставить готовый сценарий. Нажмите на значок «+» в левом верхнем углу, выберите «New workflow», щёлкните по пустому полотну и вставьте (Ctrl+V) этот JSON:
{
"nodes": [
{
"parameters": { "httpMethod": "POST", "path": "zayavka", "responseMode": "responseNode", "options": {} },
"id": "a1b2c3d4-0001-4000-8000-000000000001",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [0, 0],
"webhookId": "a1b2c3d4-0001-4000-8000-0000000000aa"
},
{
"parameters": { "respondWith": "json", "responseBody": "={{ { \"ok\": true, \"получено\": $json.body } }}", "options": {} },
"id": "a1b2c3d4-0002-4000-8000-000000000002",
"name": "Ответ",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [260, 0]
}
],
"connections": { "Webhook": { "main": [[{ "node": "Ответ", "type": "main", "index": 0 }]] } }
}
Появится окно «Pasting this from an AI tool?»: нажмите «Skip». Затем кнопка Publish в правом верхнем углу, в открывшемся окне ещё раз Publish (название версии можно оставить предложенным). Кнопка сменится на статус Published.

Что внутри. Первый узел — Webhook: метод POST, путь zayavka (последняя часть адреса, пишите её латиницей), в поле Respond выбрано «Using ‘Respond to Webhook’ Node». Второй — Respond to Webhook: он отвечает в формате JSON и возвращает присланные данные. Если хотите собрать то же руками, добавьте узлы через «+» и выставьте эти значения. Откройте узел Webhook двойным щелчком: в нём две вкладки с адресами.

Адресов два, и их легко перепутать. Test URL работает после нажатия «Listen for test event» и принимает один запрос, он нужен для отладки. Production URL работает, когда сценарий опубликован, и принимает запросы всегда. Если отправить запрос на тестовый адрес, не нажав «Listen for test event», n8n ответит:
curl -X POST https://n8n.example.com/webhook-test/zayavka -d x=1
{"code":404,"message":"The requested webhook \"zayavka\" is not registered.","hint":"Click the 'Execute workflow' button on the canvas, then try again. ..."}
Теперь проверим рабочий адрес запросом с другого компьютера:
curl -X POST https://n8n.example.com/webhook/zayavka \
-H "Content-Type: application/json" \
-d '{"имя":"Анна","телефон":"+000"}'
{"ok":true,"получено":{"имя":"Анна","телефон":"+000"}}
Сценарий принял данные и вернул их. Между двумя узлами можно вставить что угодно: запись в таблицу, письмо, сообщение в чат.
Вебхук без проверки открыт всем, кто знает адрес: в поле Authentication узла Webhook у нас стоит None. Если сценарий что-то создаёт или отправляет, выберите там Basic Auth или Header Auth и настройте учётные данные.
Резервная копия
В томе n8n_data лежат база с вашими сценариями и ключ шифрования. Копировать файл базы, пока n8n работает, нельзя: копия может выйти неполной. Поэтому остановим n8n на минуту, запакуем том и запустим снова:
mkdir -p ~/backups
docker compose stop n8n
docker run --rm -v n8n_n8n_data:/data:ro -v ~/backups:/backup alpine \
tar czf /backup/n8n-data-$(date +%F).tgz -C /data .
docker compose start n8n
Команда запускает временный контейнер alpine (--rm удалит его после работы), подключает том только для чтения (:ro) и вашу папку ~/backups, а tar czf пакует том в архив. Имя тома начинается с названия папки проекта: у нас папка называлась n8n, поэтому том n8n_n8n_data. Свои тома покажет docker volume ls. Копия базы с парой сценариев заняла 510 КБ.
Проверить, что архив читается, можно, развернув его в пробный том (подставьте дату своего архива):
docker volume create n8n_restore_test
docker run --rm -v n8n_restore_test:/data -v ~/backups:/backup:ro alpine \
sh -c "tar xzf /backup/n8n-data-2026-10-30.tgz -C /data && ls /data"
config database.sqlite database.sqlite-shm database.sqlite-wal ...
docker volume rm n8n_restore_test
Настоящее восстановление выглядит так же: распаковать архив в пустой том n8n_n8n_data до запуска n8n. Сам архив и копию .env храните вне этого сервера. О том, как вообще устроены копии на VPS, читайте в FAQ по резервным копиям.
Обновление и перезапуск
Данные хранятся в томе, поэтому контейнеры можно пересоздавать сколько угодно (только без -v, он удалил бы том). Мы остановили и снова подняли стек командами docker compose down и docker compose up -d: опубликованный сценарий остался на месте и продолжил принимать запросы.
Перед обновлением сделайте резервную копию из предыдущего раздела и посмотрите список изменений. Потом смените номер версии в compose.yaml и выполните:
docker compose pull
docker compose up -d
Прежний образ после обновления остаётся на диске и занимает почти 2 ГБ. Обычный docker image prune его не тронет: старый образ не «висячий», у него сохраняется имя с версией. Уберите его по тегу, когда убедитесь, что всё работает:
docker rmi docker.n8n.io/n8nio/n8n:2.45.7
Подставьте номер старой версии. Если образ ещё нужен какому-то контейнеру, Docker откажется его удалять.
Если что-то не работает
- Страница не открывается, в журнале Caddy ошибки сертификата. Проверьте, что A-запись указывает на сервер (
getent hosts n8n.example.com) и что открыты порты 80 и 443. - Docker не запускает Caddy из-за порта. Порт 80 или 443 занят. Если его держит программа на самом сервере, например nginx, будет написано
failed to bind host port 0.0.0.0:80/tcp: address already in use, а если другой контейнер —port is already allocated. Найдите виновника командойsudo ss -tlnp | grep -E ':(80|443) 'и остановите его либо переведите на другие порты. - Вебхуки показывают неверный адрес. Проверьте
N8N_WEBHOOK_URL: адрес должен быть публичным, сhttps://и слешем на конце. - Сценарий по расписанию срабатывает в другое время. Проверьте
GENERIC_TIMEZONEи часовой пояс самого сценария в его настройках.
Частые вопросы
Можно ли без Caddy, только с n8n?
Можно, но тогда придётся открыть наружу порт 5678, и n8n будет отвечать по обычному HTTP без шифрования. Многие сервисы отказываются присылать вебхуки на такой адрес. С Caddy HTTPS появляется сам и продлевается без вашего участия.
Нужен ли PostgreSQL?
В нашей схеме n8n хранит данные в SQLite: это встроенная база в одном файле, отдельный сервер не нужен. Для личного сервера и небольших нагрузок этого хватает. Если ожидаете много сценариев и запусков, лучше перейти на PostgreSQL, но это отдельная настройка, которую мы здесь не разбираем.
Что делать, если вы потеряли ключ шифрования?
Если сохранилась копия тома, ключ лежит в файле config внутри него. Если нет ни ключа, ни копии, сохранённые доступы расшифровать не получится и их придётся ввести заново. Сами сценарии при этом остаются.
Сколько стоит держать n8n?
Программа бесплатна для самостоятельной установки в личных и внутренних целях, платить нужно за сервер и, при желании, за домен. Актуальные цены на VPS смотрите на странице тарифов.
Что дальше
На том же сервере можно запускать и другие сервисы. Следующий в цикле — своё облако на Nextcloud. Основы работы с Docker и Compose собраны в статье «Docker и Docker Compose на VPS».
Похожее
Все статьи
Nextcloud на VPS: своё облако через Docker Compose
Nextcloud — это собственное облако: файлы с синхронизацией на телефон и компьютер, календарь, контакты, общий доступ по ссылке. На VPS его поднимают через Docker Compose из пяти контейнеров (подробности ниже): Nextcloud, база PostgreSQL, кэш Redis, контейнер для фоновых задач и…
Docker и Docker Compose на VPS с Ubuntu: установка и первый запуск
Docker на VPS ставят из официального репозитория Docker, а не из пакета docker.io, который лежит в Ubuntu: версия там свежее, а Compose приходит готовым плагином. Установка — это несколько команд, которые можно копировать подряд, ниже они разобраны по шагам. А…