MAATRIX / Блог / Dify на сервере: частые ошибки и решения

Dify на сервере: частые ошибки и решения

Dify на сервере: частые ошибки и решения

MAATRIX

Dify выглядит как одно приложение, а на сервере это десяток контейнеров, и каждый умеет ломаться молча. Кнопки консоли отвечают «Failed to fetch»; документ в базе знаний вторые сутки висит «в очереди»; после обновления пропали плагины. Ниже — ошибки Dify, которые встречаются чаще всего: симптом, причина и команда, которой это проверяется, а не угадывается.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Триаж за минуту: найти сломанный сервис, а не гадать

Поломка почти всегда сидит в одном сервисе, и первое действие — не гуглить текст ошибки, а понять, чей он.

СимптомГде смотреть в первую очередь
Сайт не открываетсяnginx, фаервол, порт 80
502 Bad Gatewayapi или web лежит, nginx жив
Кнопки дают «Failed to fetch»переменные URL в docker/.env
Документ вечно «в очереди»worker (Celery) и redis
Ошибка в узле Codesandbox
Плагин не ставится, провайдер пропалplugin_daemon, доступ в интернет
Узел HTTP Request падает на любом адресеssrf_proxy (squid)

Две команды из каталога с docker-compose.yaml:

cd /opt/dify/docker
docker compose ps -a --format "table {{.Service}}\t{{.Status}}"
docker compose logs --tail 200 --timestamps api | grep -iE "error|traceback|refused"

Ключевое — флаг -a: без него упавшие контейнеры не появятся, и вы будете чинить живой api, не заметив, что ночью умер worker. Столбец Status говорит почти всё:

СтатусЧто это значит
Up 3 hours (healthy)норма, healthcheck проходит
Restarting (1) 5 seconds agoпадает при старте, смотрите лог
Exited (137)убит сигналом KILL — почти всегда OOM
Exited (1)приложение упало само, причина в логе
Exited (0)остановлен штатно и не поднят обратно

Выясните и версию: тег в docker compose images часто latest, настоящий номер зашит в код — docker compose exec api grep CURRENT_VERSION /app/api/configs/packaging/__init__.py. В 1.0 провайдеров вынесли в плагины и добавили plugin_daemon, поэтому советы к 0.x на 1.x не годятся.

«Failed to fetch», 502 и Mixed Content: консоль есть, толку нет

Самая массовая ошибка Dify на своём сервере: интерфейс отрисовался, а вход даёт красное Failed to fetch. Откройте консоль браузера — там настоящий адрес запроса:

POST http://localhost/console/api/setup net::ERR_CONNECTION_REFUSED

Браузер стучится в localhost — то есть в ваш собственный ноутбук. Виноваты CONSOLE_API_URL, CONSOLE_WEB_URL, SERVICE_API_URL, APP_API_URL, APP_WEB_URL, FILES_URL в docker/.env: в комплектной схеме с внутренним nginx их надо оставить пустыми, тогда фронтенд ходит по относительным путям и работает с любого адреса. Совет «пропишите туда http://localhost» ломает консоль всем, кроме браузера на самом сервере.

Смежный симптом — запросы режет сам браузер:

Mixed Content: The page at 'https://dify.example.com/apps' was loaded over HTTPS,
but requested an insecure XMLHttpRequest endpoint
'http://dify.example.com/console/api/workspaces/current'.
This request has been blocked; the content must be served over HTTPS.

Так бывает, когда TLS терминируется на внешнем nginx или Caddy, а внутрь идёт голый HTTP: Dify собирает ссылки по схеме входящего запроса. Без X-Forwarded-Proto прокси не заработает, а proxy_buffering off нужен из-за SSE — иначе пользователь смотрит в пустой экран, а потом получает всю простыню разом.

proxy_set_header Host              $host;
proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
proxy_read_timeout 3600s;

Если приходит честный 502 Bad Gateway, переменные ни при чём: nginx жив, апстрим нет. Причину он пишет сам — connect() failed (111: Connection refused) while connecting to upstream, upstream: "http://172.18.0.7:5001/...". Порт 5001 — api, 3000 — web.

И правило, экономящее вечер: docker compose restart не перечитывает .env — он перезапускает процессы со старым окружением. Нужно пересоздание контейнеров; вторая команда показывает, что контейнер видит на самом деле, и часто вскрывает правку не того файла.

docker compose down && docker compose up -d
docker compose exec api env | grep -E "CONSOLE_|APP_|FILES_URL"

Развернуть за пару минут

Готовый образ на VPS MAATRIX: NVMe, AMD EPYC, root-доступ. Локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Развернуть Dify

Стек не поднимается или падает: порт, сертификат, память, диск

Консоль не открывается никак — смотрите на Docker, а не на Dify.

Порт 80 занят системным nginx или панелью:

Error response from daemon: driver failed programming external connectivity on
endpoint docker-nginx-1: Bind for 0.0.0.0:80 failed: port is already allocated

Кто держит порт — ss -ltnp | grep -E ':80|:443'. Дальше либо освобождаете 80-й, либо переносите Dify на EXPOSE_NGINX_PORT=8080.

Nginx в петле рестартов из-за сертификата. Включили NGINX_HTTPS_ENABLED=true, а файлы в docker/nginx/ssl не положили:

nginx: [emerg] cannot load certificate "/etc/ssl/fullchain.pem":
BIO_new_file() failed (SSL: error:80000002:system library::No such file or directory)

Имена задаются NGINX_SSL_CERT_FILENAME и NGINX_SSL_CERT_KEY_FILENAME. И не включайте TLS дважды: внешний прокси с сертификатом плюс NGINX_HTTPS_ENABLED=true дают ERR_TOO_MANY_REDIRECTS.

Запуск не из того каталога. Монтирования описаны относительными путями, поэтому compose-файл, скопированный в другое место, вместо конфига squid и шаблонов nginx получает пустые каталоги — ssrf_proxy и nginx уходят в рестарт.

Exited (137) — это SIGKILL, в девяти случаях из десяти от OOM-killer ядра, чаще всего у worker или plugin_daemon:

docker inspect --format='{{.State.OOMKilled}} {{.State.ExitCode}}' docker-worker-1
dmesg -T | grep -i "killed process" | tail -5

Ответ true 137 закрывает вопрос: это не баг, а нехватка памяти. Снаружи OOM не выглядит падением — убитый worker не мешает консоли открываться, документы просто «долго индексируются». Расклад по объёмам — сколько RAM нужно для Dify.

Диск. Симптомы непохожи на нехватку места: api отдаёт 500, Postgres уходит в режим только для чтения, в логе — OSError: [Errno 28] No space left on device. Смотреть надо df -h /, docker system df и du -sh /opt/dify/docker/volumes/*: место едят неротируемые логи контейнеров, старые образы и volumes/app/storage.

Пароли, миграции и SECRET_KEY: поломки после безобидной правки

Здесь живут ошибки без связи с последним вашим действием.

Смена пароля Postgres в .env. Заменили дефолтный DB_PASSWORD, пересоздали контейнеры — и в логе api:

psycopg2.OperationalError: connection to server at "db" (172.18.0.4), port 5432
failed: FATAL:  password authentication failed for user "postgres"

Опечатка ни при чём: образ Postgres применяет POSTGRES_PASSWORD только при инициализации пустого каталога данных. volumes/db/data уже создан, пароль внутри базы прежний, а api идёт с новым. Менять надо в СУБД и только потом в файле:

docker compose exec db psql -U postgres -c "ALTER USER postgres WITH PASSWORD 'новый';"

То же с REDIS_PASSWORD: он входит в строку брокера — CELERY_BROKER_URL=redis://:пароль@redis:6379/1. Поправите одну переменную из двух — задачи молча перестанут выполняться, а worker останется Up.

Миграции после обновления. При MIGRATION_ENABLED=true схема обновляется при старте api. Выключено — новый код пойдёт в старую таблицу:

sqlalchemy.exc.ProgrammingError: (psycopg2.errors.UndefinedColumn)
column workflows.marked_name does not exist

Лечение — docker compose exec api flask db upgrade, но сначала дамп: docker compose exec -T db pg_dump -U postgres dify | gzip > dify.sql.gz.

SECRET_KEY. Ею подписываются сессии и шифруются API-ключи провайдеров в базе. Пустое значение — вас разлогинивает при каждом перезапуске; смена на живой инсталляции — все ключи моделей превращаются в мусор. Теряют его классически: при обновлении копируют .env.example поверх рабочего .env. Аварийный выход — flask reset-encrypt-key-pair, и честно про цену: откатить нельзя, после неё все ключи провайдеров вводятся заново. Забытый пароль администратора чинится мягче — flask reset-password. Другие причины отказа модели: Dify не подключается к модели.

И развеем страх: данные лежат bind-монтированием в ./volumes, а не в именованных томах, поэтому docker compose down -v их не трогает. Удаляет rm -rf volumes/.

Файлы и база знаний: 413, вечная «Очередь», пустой поиск

413 при загрузке. Тянете PDF на 40 МБ и получаете 413 Request Entity Too Large. Лимитов три, поднимать надо все, иначе вы просто смените текст ошибки: NGINX_CLIENT_MAX_BODY_SIZE, UPLOAD_FILE_SIZE_LIMIT в мегабайтах (само приложение) и client_max_body_size внешнего прокси, где по умолчанию 1 МБ.

Документ висит «в очереди». Файл загрузился, статус часами не меняется, ошибок нет. Индексацию выполняет Celery — смотреть надо в worker, а не в api:

docker compose logs --tail 100 worker | grep -iE "ready|celery|error"
docker compose exec redis redis-cli -a "$REDIS_PASSWORD" llen celery

У живого воркера в логе есть строка celery@... ready., а очередь celery не растёт монотонно. Растущая очередь при пустом логе значит, что воркер не подключился к брокеру — сверяйте пароль в CELERY_BROKER_URL. Воркер жив, а задачи исчезают — ищите Exited (137).

Индексация падает на модели эмбеддингов. В режиме High Quality каждый чанк уходит в модель, и если роль Embedding Model не назначена или у провайдера кончилась квота, документ уходит в ошибку целиком. Для документации с точной терминологией выручает режим Economy: индекс по ключевым словам, к модели не обращается и стоит ноль. На разговорных вопросах он заметно слабее.

Поиск пуст после смены векторного хранилища. VECTOR_STORE переключает движок (weaviate, qdrant, milvus, pgvector), но данные не переносит: метаданные остаются в Postgres, векторов в новом хранилище нет, поиск отдаёт пустоту без ошибок в логе. Лечится повторной индексацией — с повторным счётом за эмбеддинги.

Плагины, sandbox и таймауты: что ломается в ветке 1.x

После перехода на 1.x появился класс ошибок, которых в 0.x не было.

Плагин не ставится, провайдер моделей исчез из списка. Установка идёт через plugin_daemon, качающий пакет с marketplace.dify.ai. Нет доступа наружу — установка виснет и падает по таймауту, а в логе api:

requests.exceptions.ConnectionError: HTTPConnectionPool(host='plugin_daemon', port=5002):
Max retries exceeded

Проверка: docker compose exec api curl -sI https://marketplace.dify.ai | head -1. Ответа нет — чинить сеть. Обходной путь: скачать .difypkg и поставить через «Установить из локального файла»; для пакета без подписи нужен ещё FORCE_VERIFYING_SIGNATURE=false — а это отключение проверки происхождения кода, который исполнится у вас на сервере.

Узел Code возвращает ошибку. Исполняет его контейнер sandbox, а не api. Сбоя два: рассинхрон ключей (SANDBOX_API_KEY песочницы и CODE_EXECUTION_API_KEY у api должны совпадать) и отсутствующая библиотека — зависимости из кода песочница не ставит, список пакетов лежит в docker/volumes/sandbox/dependencies/python-requirements.txt, после правки нужен up -d --force-recreate sandbox.

Узел HTTP Request падает на любом адресе. Трафик таких узлов идёт через ssrf_proxy — squid на порту 3128, не пускающий приложение во внутреннюю сеть и к метаданным облака. Контейнер лёг — разом умирают все HTTP-узлы во всех приложениях, хотя сервер в интернет ходит прекрасно.

504 и обрыв длинных ответов. Таймауты живут в нескольких местах, лечить надо сработавший первым: NGINX_PROXY_READ_TIMEOUT, proxy_read_timeout внешнего прокси, GUNICORN_TIMEOUT у api, TEXT_GENERATION_TIMEOUT_MS для модели и WORKFLOW_MAX_EXECUTION_TIME для workflow. Ошибка ровно на шестидесятой секунде — дефолт внешнего nginx, а не настройки Dify.

Какой сервер под Dify брать в MAATRIX

Половина разобранных ошибок — не конфигурация, а следствие тесной машины: OOM у воркера, отвалившийся plugin_daemon, вечная «Очередь». Считать надо по составу стека: Postgres, векторное хранилище, фронтенд, два Python-процесса и демон плагинов.

Честный минимум: 2 vCPU, 4 ГБ RAM, 40 ГБ NVMe — ровно документационный порог разработчиков. Стек поднимется, один-два чат-бота на внешнем API отработают нормально. Чего не будет — запаса на индексацию: worker разбирает крупный PDF, Postgres и векторная база пишут параллельно, память кончается, документ остаётся в ошибке. Swap здесь обязателен, плагинов держите минимум.

Комфортный вариант: 4 vCPU, 8 ГБ RAM, 80–100 ГБ NVMe. Нужен, когда базой знаний пользуются каждый день, документы приходят пачками и стоит десяток плагинов. Появляется резерв, гасящий OOM заранее. По диску закладывайте 1–2 ГБ в месяц на активную базу плюс место под образы. Локальную модель рядом на 8 ГБ не разместить: 7B в Q4 — ещё около 4,5 ГБ только весами.

Локация — UK, Лондон. Короткий путь до маркетплейса плагинов и европейских API, отсутствие отказов вида unsupported_country_region_territory, которыми провайдеры моделей встречают российский IP, и понятный GDPR-контур. Трафик в основном к американским сервисам — берите US, Нью-Йорк с чистым IP; персональные данные россиян и 152-ФЗ — только RU.

Dify выбирается из каталога apps.maatrix.io при оформлении сервера и разворачивается автоматически на Ubuntu или Debian; адрес консоли и стартовые доступы появляются в личном кабинете, в разделе «Доступ». Оплата — картой российского банка, по СБП, криптовалютой или токеном MAAT. Сразу после первого входа положите SECRET_KEY в менеджер паролей и смените дефолтные пароли базы и redis через ALTER USER — задним числом это чинится дороже всего. Ручная установка разобрана отдельно: установка Dify на VPS.

Развернуть за пару минут

Готовый образ на VPS MAATRIX: NVMe, AMD EPYC, root-доступ. Локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Развернуть Dify

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Частые вопросы

Поправил .env, сделал docker compose restart — ничего не изменилось. Почему?

restart перезапускает процессы со старым окружением: переменные читаются только при создании контейнера. Нужны docker compose down и docker compose up -d, проверка — docker compose exec api env | grep SECRET_KEY.

Как отличить OOM от обычного падения контейнера?

docker compose ps -a покажет Exited (137), а docker inspect --format='{{.State.OOMKilled}}' docker-worker-1 ответит true; подтверждение — Killed process в dmesg -T. Код 1 значит, что упало само приложение, и причина будет в логе.

После обновления модели отвалились с ошибкой расшифровки.

Почти наверняка рабочий .env перезаписали свежим .env.example и потеряли SECRET_KEY. Есть старое значение в бэкапе — верните его и пересоздайте контейнеры. Нет — остаётся flask reset-encrypt-key-pair и повторный ввод ключей провайдеров; сами приложения и базы знаний не теряются.

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.