Open WebUI на удалённом Linux-сервере

Поможем открыть Open WebUI на удалённом Linux-сервере через SSH-туннель и разобраться, как связаны доступ, работа контейнера и сохранение данных.

После урока вы сможете

  • Различать адреса своего компьютера, серверного хоста и контейнера
  • Открывать существующий серверный экземпляр через локальный SSH-туннель
  • Отделять время жизни туннеля от работы Docker и сохранения данных
  • Проверять доступ, готовность приложения и подключение модели отдельно

Проверяемый результат: Понятно, как открыть свой серверный Open WebUI, где выполнять команды и что изменится после закрытия SSH-туннеля.

Почему Open WebUI отвечает на сервере, а тот же localhost в моём браузере его не открывает? Когда браузер и контейнер работают на разных машинах, адрес 127.0.0.1 в адресной строке ведёт к вашему собственному компьютеру, а не к серверу. Чтобы открыть уже работающий серверный экземпляр и не публиковать его порт в интернет, проложим между двумя компьютерами личный SSH-туннель.

Для урока нужен свой сервер Ubuntu 24.04, к которому у вас есть разрешённый и проверенный доступ по SSH. Для вашей учётной записи должно быть разрешено локальное перенаправление портов (local TCP forwarding), а в папке ~/projects/open-webui уже должен работать проект из предыдущих материалов. На удалённом сервере контейнер работает на другой машине. Способы запуска через Docker или Python и отдельное подключение языковых моделей остаются прежними, как описано в руководстве по развёртыванию Open WebUI.

Если до этого приложение работало только на вашем ноутбуке, на удалённый хост оно автоматически не перенеслось. На новом сервере сначала пройдём подготовку Docker для Linux и развернём проект по уроку установки Open WebUI через Docker Compose прямо в серверном SSH-терминале, а затем вернёмся сюда перед открытием браузера.

Проверим работу Open WebUI на сервере

Сначала убедимся, что на сервере работает нужный контейнер и приложение готово отвечать на запросы. Затем настроим путь к нему со своего компьютера. Войдём на сервер под своим обычным пользователем с теми же параметрами порта и SSH-ключа, которые вы уже проверили раньше. Как описано в руководстве ssh(1) и справочнике ssh_config(5), при первом подключении или при предупреждении о смене ключа сверяем отпечаток (fingerprint) с доверенными данными от администратора или из панели провайдера. Отключать проверку ключа флагом StrictHostKeyChecking=no или удалять записи наугад не будем.

На своём компьютере откроем терминал и подключимся к серверу (замените courseuser и server.example.com на имя пользователя и адрес вашего сервера):

ssh courseuser@server.example.com

После входа перейдём в существующую папку проекта и посмотрим список его контейнеров. Если в подготовительном уроке вы обращались к Docker через sudo, сохраним этот способ и здесь.

На сервере проверим каталог и статус контейнера через docker compose ps:

cd ~/projects/open-webui
pwd
sudo docker compose ps --all

Флаг --all показывает все контейнеры проекта, включая остановленные. Команда pwd должна вывести полный путь к папке ~/projects/open-webui, а sudo docker compose ps --all показать запущенный сервис open-webui с привязкой 127.0.0.1:3000->8080/tcp в колонке портов. Если каталог не найден, список пуст, контейнер остановлен или порты отличаются, прервёмся и проверим, на тот ли сервер и в тот ли проект мы вошли. Создавать новую папку или запускать docker compose up поверх неизвестного состояния не нужно.

Теперь проверим, готово ли приложение отвечать по HTTP на самом сервере. В исходном коде Open WebUI 0.12.3 (commit 91ea4e34) в файле backend/open_webui/main.py маршрут /ready проверяет завершение старта приложения, выполняет запрос SELECT 1 к базе данных и проверяет Redis, если он настроен. Этот маршрут подтверждён для версии 0.12.3; если у вас установлена другая версия, сначала сверьтесь с её документацией и не обновляйте образ специально ради этой проверки. Обычный маршрут /health в этом коде всегда возвращает только статический ответ, а запрос к произвольному неизвестному адресу может вернуть HTML-оболочку интерфейса с кодом 200, поэтому для проверки готовности они не подходят.

На сервере запросим маршрут /ready с помощью curl:

curl --fail --silent --show-error --max-time 10 http://127.0.0.1:3000/ready

Флаг --fail заставляет curl завершаться с ошибкой при HTTP-кодах вроде 503. Пара --silent и --show-error скрывает индикатор загрузки, но выводит текст ошибки при сбое. Флаг --max-time 10 ограничивает ожидание десятью секундами. Для готового экземпляра поддерживаемой версии ожидаем успешный ответ JSON со значением status: true. Проверка подтверждает готовность веб-приложения и его базы данных на сервере. Языковую модель она не вызывает.

Свяжем свой компьютер с сервером через SSH-туннель

Когда приложение отвечает на сервере, разберёмся, почему одинаковый адрес 127.0.0.1 ведёт в разные места. Loopback-интерфейс всегда указывает на то устройство, где выполняется запрос. Чтобы различать локальный вход туннеля и серверный порт, выберем на своём компьютере свободный порт 13000. Путь запроса пройдёт через три точки:

ГдеАдрес/портРоль
Компьютер с браузером127.0.0.1:13000Локальный вход SSH-туннеля
Удалённый Linux-хост127.0.0.1:3000Опубликованный порт прежнего Compose
Контейнер Open WebUI8080Порт приложения внутри контейнера

Проследим путь запроса целиком. Браузер на вашем компьютере обращается к локальному адресу 127.0.0.1:13000. Запущенный там же клиент OpenSSH принимает это соединение, переносит его по зашифрованному SSH-каналу на удалённый сервер и со стороны сервера обращается к 127.0.0.1:3000. Правило публикации портов из нашего compose.yaml в стандартной bridge/NAT-сети передаёт трафик с серверного порта 3000 на порт 8080 внутри контейнера.

Поскольку на сервере порт привязан к 127.0.0.1, открывать порт 3000 в панели управления облачного провайдера или в межсетевом экране не требуется. В документации Docker по публикации портов отмечено, что актуальный Docker Engine включает исправление из версии 28.0.0, тогда как в более старых выпусках порты на localhost могли оставаться достижимыми из того же сегмента локальной сети (L2). Привязка к loopback-адресу также не изолирует порт от других пользователей, у которых уже есть доступ к этому серверному хосту.

Для запуска туннеля откроем второе окно терминала на том же компьютере, где вы будете открывать браузер. Вводить эту команду внутри уже открытой серверной SSH-сессии не нужно. Номер 13000 выбран как свободный локальный порт на вашем компьютере; серверный порт 3000 и контейнерный 8080 остаются без изменений. Если при обычном входе вы указывали нестандартный порт SSH (-p) или путь к ключу (-i), добавьте эти же флаги в команду.

На своём компьютере запустим перенаправление портов в новом локальном терминале:

ssh -N -o ExitOnForwardFailure=yes -L 127.0.0.1:13000:127.0.0.1:3000 courseuser@server.example.com

Разберём параметры по документации ssh(1) и ssh_config(5):

  • -N указывает клиенту не запускать командную оболочку на сервере, так как это соединение нужно нам только для пересылки трафика.
  • -L 127.0.0.1:13000:127.0.0.1:3000 открывает порт 13000 только на локальном адресе вашего компьютера и перенаправляет входящие соединения на адрес 127.0.0.1:3000 со стороны сервера.
  • -o ExitOnForwardFailure=yes завершает работу SSH-клиента с ошибкой, если ему не удалось занять локальный порт или согласовать правило перенаправления. При этом сам параметр проверяет только создание туннеля, а не работоспособность контейнера Open WebUI, и не гарантирует закрытие клиента, если приложение на сервере остановится позже.

В нашем учебном сценарии используется обычный запуск SSH на переднем плане (foreground) без заранее настроенных фоновых мультиплексоров ControlMaster и ControlPersist. После успешного подключения команда может ничего не выводить и не возвращать приглашение ввода. Оставим это окно терминала открытым. По документации sshd_config(5), разрешение на перенаправление портов (AllowTcpForwarding) настраивается на сервере отдельно от права на вход в терминал. Если вход в оболочку работает, а туннель запрещён политикой сервера, обратитесь к администратору; настройки демона sshd в этом уроке мы не меняем.

Откроем серверный интерфейс в своём браузере

Пока в локальном терминале работает команда туннеля, перейдём к браузеру на этом же компьютере. Локальный порт 13000 теперь ведёт к нашему серверному экземпляру, и сам туннель не создаёт ни новых учётных записей, ни отдельной базы данных.

На своём компьютере откроем адрес в браузере:

http://127.0.0.1:13000

Ожидаем увидеть знакомую страницу входа в Open WebUI и войти под своей существующей учётной записью. Порядок регистрации и базовые настройки разобраны в уроке про первый вход администратора. Повторять проверку через curl на локальном компьютере не требуется. Если же вместо окна входа браузер предлагает создать первого администратора с нуля, сразу остановитесь и ничего не заполняйте: это признак того, что вы подключились к другому экземпляру или контейнер запустился с пустым томом данных.

Различим четыре проверки:

  1. Работающий статус контейнера в Docker показывает, что процесс запущен.
  2. Успешный ответ /ready на сервере подтверждает, что приложение прошло инициализацию и видит свою базу данных.
  3. Открытие страницы проверяет путь через туннель, а вход в знакомую учётную запись помогает убедиться, что мы попали в нужный экземпляр. Сам по себе вход не подтверждает, что секретный ключ остался прежним.
  4. Ответ языковой модели в чате проверяет отдельное соединение между сервером Open WebUI и сервером моделей. В этом уроке мы не вызываем модель. Открытие интерфейса в браузере ноутбука не переносит на него вычисления (инференс). Успешный вход в аккаунт также не проверяет полноту архивной истории или восстановление из резервной копии.

Что продолжит работать после закрытия туннеля

Серверный Open WebUI продолжит работать после закрытия локального SSH-клиента. Окно туннеля нужно для доступа из браузера; контейнером на сервере управляет Docker.

Когда вы закончите работу в браузере, вернитесь в локальное окно терминала, где запущен туннель.

На своём компьютере завершим работу foreground-туннеля сочетанием клавиш:

Ctrl+C

После нажатия Ctrl+C клиент OpenSSH закроет соединение, и новые запросы из браузера к http://127.0.0.1:13000 перестанут доходить до сервера. Открытая ранее вкладка может оставаться на экране из памяти браузера. Видимая страница сама по себе не означает, что связь с сервером ещё активна.

Когда вам снова понадобится открыть интерфейс, переустанавливать проект или перезапускать контейнер не придётся.

На своём компьютере достаточно повторить запуск туннеля и снова открыть адрес в браузере:

ssh -N -o ExitOnForwardFailure=yes -L 127.0.0.1:13000:127.0.0.1:3000 courseuser@server.example.com

На самом сервере контейнер работает в фоновом режиме (detached) под управлением системной службы Docker Engine. Как объясняется в документации Docker по автозапуску контейнеров и в руководстве по послеустановочной настройке Linux, постоянная работа сервиса складывается из двух условий: сама служба docker включена в автозагрузку Ubuntu через systemd, а для контейнера в compose.yaml задана политика restart: unless-stopped. Эта политика перезапускает контейнер при сбое или после перезапуска демона Docker. Если вы сами остановили проект командой stop, автоматически он не включится. Политика перезапуска не проверяет внутреннюю готовность приложения и не заменяет резервное копирование. Писать поверх Docker отдельный файл службы systemd для контейнера или настраивать пользовательский linger (который применяется в других сценариях, вроде пользовательских служб Python или Podman) здесь не нужно.

Сохранность ваших чатов, настроек и учётных записей тоже не зависит от SSH-туннеля. Как указано в руководстве быстрого старта и в рекомендациях по укреплению безопасности Open WebUI, данные хранятся внутри контейнера по пути /app/backend/data, куда подключён постоянный том Docker, а сессии опираются на сохранённый WEBUI_SECRET_KEY. Сохраним прежние проект, том и значение этого ключа; сам том не заменяет резервную копию. Папка ~/projects/open-webui с файлом compose.yaml содержит только конфигурацию запуска, тогда как сами данные лежат в томе под управлением Docker.

Если вам нужно убедиться, что служба Docker на сервере активна и добавлена в автозагрузку, это можно проверить стандартными командами systemd.

На сервере (при необходимости проверки службы движка):

systemctl is-active docker
systemctl is-enabled docker

В ожидаемом выводе первая команда вернёт active, а вторая enabled. Чтобы проверить политику перезапуска и подключённое хранилище контейнера без вывода секретных переменных окружения, воспользуемся точечным форматированием docker inspect.

На сервере (при необходимости проверить настройки перезапуска и том контейнера) сначала узнаем идентификатор контейнера, а затем подставим полученную строку вместо CONTAINER_ID:

sudo docker compose ps -q open-webui
sudo docker inspect --type container --format '{{.HostConfig.RestartPolicy.Name}}' CONTAINER_ID
sudo docker inspect --type container --format '{{range .Mounts}}{{println .Type .Name .Destination}}{{end}}' CONTAINER_ID

Первая команда с флагом -q выведет идентификатор работающего контейнера сервиса. Во второй команде ожидаем увидеть политику unless-stopped, а в третьей строку с типом volume, именем прежнего тома и точкой назначения /app/backend/data. Имя зависит от проекта; сверяем его со своей прежней конфигурацией. Если контейнер остановлен, сначала вернёмся к выводу sudo docker compose ps --all.

Разберём ошибки подключения

Если интерфейс не открылся, начните с первого неподтверждённого звена в цепочке. Переустанавливать Docker или пересоздавать проект не нужно:

  • Не удаётся войти по SSH или появилось предупреждение о смене ключа хоста. Проверьте адрес сервера, имя пользователя, порт и ключ, которыми вы пользовались раньше. Если терминал сообщает об изменении ключа сервера (REMOTE HOST IDENTIFICATION HAS CHANGED), остановитесь и сверьте отпечаток ключа через консоль провайдера или у администратора. Не обходите эту проверку и не переустанавливайте Open WebUI: ошибка возникла ещё до обращения к контейнеру.
  • При запуске туннеля появляется Address already in use. Это значит, что локальный порт 13000 на вашем компьютере уже занят другим окном терминала со старым туннелем или другой программой. Закройте дублирующий туннель или выберите другой свободный локальный порт (например, замените 13000 на 13001 в левой части флага -L и открывайте http://127.0.0.1:13001 в браузере). Менять порт 3000 в серверном compose.yaml из-за локального конфликта не нужно.
  • В терминале появляется administratively prohibited или отказ в создании forward. Ваша учётная запись или конфигурация SSH-сервера запрещает перенаправление TCP-портов, даже если обычный вход в командную строку работает. Этот вопрос решается с администратором сервера.
  • Команда curl на сервере не возвращает успех на /ready или в окне туннеля мелькает channel open failed: connect failed. Успешный запуск SSH-клиента не означает, что само приложение готово. Если контейнер только что запустился, подождите завершения инициализации и повторите проверку curl. Если ошибка сохраняется, проверьте по документации установленной версии, поддерживает ли она маршрут /ready, и обратитесь к журналу контейнера по предыдущему уроку. Плавающий тег вроде ghcr.io/open-webui/open-webui:main сам по себе не доказывает точный номер установленного релиза.

На сервере (если при диагностике нужно проверить контекст и версии Docker или повторить запрос готовности):

sudo docker version
sudo docker compose version
sudo docker context ls
curl --fail --silent --show-error --max-time 10 http://127.0.0.1:3000/ready
  • На сервере /ready отвечает успешно, а в браузере страница не открывается. Проверьте, что второе локальное окно терминала с командой ssh -N ... остаётся открытым на том же компьютере, где запущен браузер, и что в адресной строке указан именно локальный порт туннеля http://127.0.0.1:13000, а не 3000 и не https://.
  • Вместо входа открылась регистрация первого администратора. Остановитесь, не создавайте новый аккаунт и проверьте по шагам выше, в той ли папке проекта на сервере запущен контейнер и какой том примонтирован в /app/backend/data.
  • Интерфейс открылся, вы вошли в аккаунт, но модель не отвечает. Ваш личный доступ к Open WebUI через SSH работает исправно. Доступность сервера моделей настраивается и диагностируется отдельно от веб-интерфейса.

Личный доступ одного администратора или разработчика устроен так: серверный контейнер и его хранилище остаются на месте, а интерфейс вы открываете по адресу http://127.0.0.1:13000 через зашифрованный SSH-туннель. Как отмечается в документации по планированию развёртывания, сетевой защите и настройке HTTPS с обратным прокси, для совместной работы команды или доступа через собственный домен потребуются отдельные решения по TLS-сертификатам, аутентификации, резервному копированию и мониторингу.

Материалы урока

Отметить прохождение

Отметьте урок после того, как получили проверяемый результат из практической части.

body>