Dealownia
Menu
sprawdzono – jak sprawdzamy oferty

Open WebUI z Ollamą na własnym serwerze: instalacja i HTTPS

Open WebUI w Docker Compose z Ollamą albo samym API: nginx z HTTPS, konta i zatwierdzanie użytkowników, podpięcie OpenAI, Claude i Gemini oraz kopie danych.

AI i modelezaktualizowano 10 min czytania

Redakcja serwisu Dealownia · ceny i fakty ze stron dostawców, jak sprawdzamy

Trzy osoby w domu albo w małej firmie, każda z osobnym abonamentem czatu, i nikt nie wie, czy z niego naprawdę korzysta. Open WebUI rozwiązuje to jednym interfejsem w przeglądarce: konta dla każdego, wspólna lista modeli, a za nimi lokalna Ollama, płatne API w modelu „płacisz za tokeny” albo jedno i drugie. Poniżej stawiamy go pod https://czat.example.pl w Docker Compose, za nginx z certyfikatem, z zatwierdzaniem nowych kont i z kopią, z której da się odtworzyć rozmowy.

Czego potrzebujesz

  • VPS przygotowany jak w poradniku od zera: użytkownik deploy z sudo, ufw, nginx na hoście, Docker z Compose.
  • RAM. Według dokumentacji Open WebUI 1 GB to bardzo mało, gdy działają dokumenty (RAG) i embeddingi, i lepiej mieć 2 GB lub więcej na sam interfejs. Lokalny model doliczasz osobno – ile pamięci potrzebuje który, pokazuje tabela w poradniku o Ollamie na VPS bez GPU. Jeśli korzystasz tylko z API, wystarczy plan z 2–4 GB.
  • Dysk. Standardowy obraz pobiera się według dokumentacji w ok. 1,6 GB, do tego modele Ollamy i przesłane pliki.
  • Subdomenę czat.example.pl z rekordem A wskazującym serwer.

Pełne porównanie planów jest w rankingu VPS, a roczny koszt serwera z domeną policzysz w kalkulatorze kosztu hostingu.

Krok 1. Katalog i klucz

bash
sudo mkdir -p /opt/apps/openwebui && sudo chown deploy:deploy /opt/apps/openwebui
cd /opt/apps/openwebui
umask 077
printf 'WEBUI_SECRET_KEY=%s\n' "$(openssl rand -hex 32)" > .env

WEBUI_SECRET_KEY podpisuje tokeny logowania i służy do szyfrowania części danych (np. sesji OAuth). Open WebUI potrafi wygenerować go sam, ale dokumentacja zaleca wygenerować go raz i trzymać stały. Bez stałego klucza po każdym odtworzeniu kontenera wszyscy zostają wylogowani, a przy przywracaniu kopii potrzebny jest ten sam klucz. Plik .env ma prawa 600, bo tak ustawił umask 077.

Krok 2. compose.yaml z Ollamą

yaml
services:
  open-webui:
    image: ghcr.io/open-webui/open-webui:main
    restart: unless-stopped
    ports:
      - "127.0.0.1:3003:8080"
    environment:
      WEBUI_SECRET_KEY: ${WEBUI_SECRET_KEY}
      WEBUI_URL: https://czat.example.pl
      CORS_ALLOW_ORIGIN: https://czat.example.pl
      OLLAMA_BASE_URL: http://ollama:11434
      DEFAULT_USER_ROLE: pending
      ENABLE_COMMUNITY_SHARING: "false"
    volumes:
      - open-webui:/app/backend/data
    depends_on:
      - ollama

  ollama:
    image: ollama/ollama:latest
    restart: unless-stopped
    environment:
      OLLAMA_KEEP_ALIVE: 30m
      OLLAMA_MAX_LOADED_MODELS: 1
    volumes:
      - ollama:/root/.ollama

volumes:
  open-webui:
  ollama:

Co tu jest ważne:

  • Port 3003 na 127.0.0.1. Dokumentacja Open WebUI używa w przykładach portu 3000, ale ten numer zajmuje już Umami z poradnika o analityce, a 3001 – Uptime Kuma. Jeśli na serwerze działa coś jeszcze, sprawdź wolne porty poleceniem sudo ss -tlnp. Kontener nasłuchuje na 8080, a na zewnątrz wystawia go tylko nginx.
  • Ollama bez opublikowanego portu. Open WebUI łączy się z nią po nazwie usługi (http://ollama:11434) w sieci, którą Compose tworzy dla projektu. Lokalne API Ollamy nie ma uwierzytelnienia, więc tak jest najbezpieczniej.
  • DEFAULT_USER_ROLE: pending. Nowe konto nie dostaje dostępu do modeli, dopóki administrator go nie zatwierdzi. Dokumentacja poleca to ustawienie dla instancji dostępnych z internetu.
  • ENABLE_COMMUNITY_SHARING: "false" wyłącza udostępnianie rozmów poza instancję. Tak radzi poradnik dokumentacji o pracy w zespole.
  • CORS_ALLOW_ORIGIN. Bez tej zmiennej, według poradnika nginx w dokumentacji, połączenia WebSocket potrafią nie działać, nawet gdy proxy je przepuszcza. Hardening zaleca też zastąpienie domyślnego * własną domeną.
  • main i latest. Tag main (tak samo jak latest) to bieżąca kompilacja gałęzi main, a nie wybrane stabilne wydanie, i może bez ostrzeżenia przynieść zmiany niezgodne wstecz. Gdy wszystko działa, przypnij konkretne wydanie (ghcr.io/open-webui/open-webui:vX.Y.Z) – tak zaleca dokumentacja aktualizacji.

Masz już Ollamę z poprzedniego poradnika? Kontener z katalogu /opt/apps/ollama ma port tylko na 127.0.0.1 hosta, a z wnętrza innego kontenera ten adres nie jest osiągalny. Najprościej zatrzymać tamten projekt (cd /opt/apps/ollama && docker compose down) i w tym pliku podpiąć jego wolumen, żeby nie pobierać modeli drugi raz. Zamień ostatnią sekcję na:

yaml
volumes:
  open-webui:
  ollama:
    external: true
    name: ollama_ollama

Nazwę wolumenu sprawdzisz przez docker volume ls – Compose nazywa go <katalog projektu>_<nazwa wolumenu>. Jeśli korzystałeś z tunelu SSH albo z nginx z tokenem z tamtego poradnika, dopisz w usłudze ollama sekcję ports: ["127.0.0.1:11434:11434"] – inaczej po zatrzymaniu starego projektu ten adres przestanie odpowiadać.

Tylko API, bez lokalnego modelu? Usuń usługę ollama, całą sekcję depends_on, wolumen ollama i zmienną OLLAMA_BASE_URL, a dodaj ENABLE_OLLAMA_API: "false". Dokumentacja wspomina też o lżejszym obrazie :slim (ok. 200 MB zamiast 1,6 GB), ale nie ma w nim lokalnych embeddingów, rozpoznawania mowy ani przetwarzania dokumentów – te funkcje wymagają wtedy zewnętrznych usług, więc na start zostań przy main.

bash
docker compose up -d
docker compose logs -f open-webui

Pierwszy start trwa dłużej, bo obraz jest duży. Czekaj, aż w logu przestaną pojawiać się komunikaty o inicjalizacji, i sprawdź curl -sI http://127.0.0.1:3003 | head -1.

Krok 3. nginx z WebSocketem i certyfikat

Open WebUI używa połączeń WebSocket i strumieniuje odpowiedzi, więc proxy musi przekazywać nagłówki Upgrade i Connection i nie może buforować odpowiedzi. Mapę WebSocket tworzysz raz na serwer (wyjaśnienie w poradniku o reverse proxy); polecenie niżej nic nie zmieni, jeśli plik już istnieje:

bash
[ -f /etc/nginx/conf.d/websocket-map.conf ] || sudo tee /etc/nginx/conf.d/websocket-map.conf >/dev/null <<'EOF'
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}
EOF

Plik /etc/nginx/sites-available/czat.example.pl:

nginx
server {
    listen 80;
    listen [::]:80;
    server_name czat.example.pl;

    client_max_body_size 20M;

    location / {
        proxy_pass http://127.0.0.1:3003;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $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;
        proxy_buffering off;
        proxy_cache off;
        proxy_connect_timeout 1800;
        proxy_send_timeout 1800;
        proxy_read_timeout 1800;
    }
}
bash
sudo ln -s /etc/nginx/sites-available/czat.example.pl /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d czat.example.pl

Wartości buforowania, limitów czasu (30 minut) i rozmiaru przesyłanych plików (20 MB) pochodzą z przykładu nginx w dokumentacji Open WebUI. Dokumentacja wyjaśnia, że włączone buforowanie potrafi pociąć strumieniowaną odpowiedź: znikają słowa i psuje się formatowanie. Jeśli użytkownicy wgrywają większe PDF-y, podnieś client_max_body_size.

Krok 4. Konto administratora i użytkownicy

Wejdź na https://czat.example.pl od razu po wystawieniu certyfikatu. Pierwsze założone konto zostaje administratorem: zarządza użytkownikami i wszystkimi ustawieniami instancji. Do tego czasu rejestracja jest otwarta i konto może przejąć każdy, kto trafi na adres. Po założeniu konta administratora Open WebUI sam wyłącza rejestrację.

Kolejne osoby dodajesz na dwa sposoby:

  • Ręcznie: w panelu administratora, w sekcji użytkowników, zakładasz konto i przekazujesz hasło.
  • Rejestracja z zatwierdzaniem: w ustawieniach administratora, w sekcji uwierzytelniania, włączasz nowe rejestracje. Nowe konta mają rolę pending i czekają, aż je zatwierdzisz. Po zaproszeniu wszystkich rejestrację wyłącz.

Zmienne ENABLE_SIGNUP i DEFAULT_USER_ROLE działają tylko przy pierwszym starcie. Później wygrywają ustawienia zapisane w bazie, więc zmiana w compose.yaml nic nie da – zmieniasz je w panelu. Dokumentacja hardeningu wymienia jeszcze kilka rzeczy wartych włączenia na instancji dostępnej z internetu: krótszy czas ważności sesji (JWT_EXPIRES_IN, domyślnie 4 tygodnie), walidację haseł i uwierzytelnianie dwuskładnikowe. Najlepiej w ogóle nie wystawiać panelu do internetu: jeśli korzystasz tylko Ty i rodzina, wystarczy VPN albo tunel, a strona nie musi mieć publicznego rekordu DNS.

Krok 5. Zewnętrzne API zamiast lokalnego modelu

Lokalny model na CPU jest wolny i mniej zdolny od dużych modeli w chmurze. Open WebUI łączy się z każdym API zgodnym z OpenAI, w tym z API Anthropic i Google. W panelu administratora wejdź w ustawienia, potem Connections → Manage OpenAI API Connections → Add Connection, wpisz adres i klucz API, zapisz.

Dostawca Adres (z dokumentacji Open WebUI) Uwagi
OpenAI https://api.openai.com/v1 domyślny adres zmiennej OPENAI_API_BASE_URL
Anthropic (Claude) https://api.anthropic.com/v1 warstwa zgodności OpenAI; Anthropic przeznacza ją do testów i oceny, bez cache promptów i natywnej obsługi PDF
Google Gemini https://generativelanguage.googleapis.com/v1beta/openai bez ukośnika na końcu, inaczej lista modeli się nie pobierze
DeepSeek https://api.deepseek.com/v1
Mistral AI https://api.mistral.ai/v1
OpenRouter https://openrouter.ai/api/v1 jeden klucz do modeli wielu firm

Kilka zasad przy kluczach:

  • Załóż osobny klucz tylko dla Open WebUI. Dokumentacja odradza używanie kluczy głównych z pełnymi uprawnieniami.
  • Jeśli dostawca pozwala ustawić limit wydatków albo działa na doładowaniach, skorzystaj z tego. Kilka osób na jednym kluczu potrafi wydać więcej, niż zakładasz.
  • W polu Model IDs połączenia wpisz tylko modele, które chcesz pokazać. Pełna lista u dużych dostawców ma dziesiątki pozycji, w tym drogie modele.
  • Weryfikacja połączenia czasem kończy się błędem 400, 401 albo 403, choć rozmowy działają – dokumentacja opisuje to wprost. Sprawdzaj wysłaniem wiadomości, nie samym przyciskiem.

Ile kosztuje taka rozmowa i kiedy API wychodzi taniej niż abonament, liczy poradnik ile kosztuje API modeli AI w złotych. Ceny abonamentów porównasz w rankingu asystentów AI.

Krok 6. Kopie danych

Rozmowy, użytkownicy, ustawienia i przesłane pliki leżą w wolumenie zamontowanym jako /app/backend/data: baza webui.db (SQLite), katalogi uploads/ i vector_db/. Dokumentacja odradza pobieranie bazy z panelu administratora jako kopii: ostatnie zmiany mogą jeszcze siedzieć w pliku webui.db-wal, więc pobrany plik może ich nie mieć. Sam webui.db nie obejmuje też przesłanych plików ani bazy wektorowej – te leżą w uploads/ i vector_db/. Najbardziej spójną kopię daje według dokumentacji kopiowanie przy zatrzymanej usłudze, więc skrypt zatrzymuje kontener na kilka sekund i pakuje cały wolumen.

Skrypt /opt/apps/openwebui/backup.sh:

bash
#!/usr/bin/env bash
set -euo pipefail
umask 077
cd /opt/apps/openwebui
mkdir -p backup
D=$(date +%F)
trap 'docker compose start open-webui' EXIT
docker compose stop open-webui
docker run --rm -v openwebui_open-webui:/data:ro -v "$PWD/backup:/backup" alpine \
  tar -czf "/backup/openwebui-$D.tar.gz" -C /data .
cp .env "backup/env-$D"
find backup -mindepth 1 -maxdepth 1 -mtime +7 -delete
bash
chmod 700 backup.sh && sudo ./backup.sh && sudo ls -lh backup
echo "30 4 * * * root /opt/apps/openwebui/backup.sh >>/var/log/openwebui-kopia.log 2>&1" | sudo tee /etc/cron.d/openwebui-kopia

trap uruchomi kontener ponownie także wtedy, gdy pakowanie się nie uda. Kopia .env jest potrzebna, bo bez tego samego WEBUI_SECRET_KEY po przywróceniu wszyscy będą musieli zalogować się od nowa, a dane zaszyfrowane tym kluczem nie dadzą się odczytać. Modeli Ollamy nie kopiujesz – pobierzesz je ponownie. Archiwum wyślij poza serwer, np. jak w poradniku o restic i rclone.

Przed każdą aktualizacją uruchom skrypt ręcznie, a potem:

bash
docker compose pull && docker compose up -d

Według dokumentacji migracje bazy działają tylko w jedną stronę. Powrót do starszego obrazu ich nie cofnie, więc wtedy ratuje Cię tylko kopia sprzed aktualizacji.

Najczęstsze problemy

  • Po restarcie kontenera wszyscy są wylogowani. Brak stałego WEBUI_SECRET_KEY albo kontener nie widzi pliku .env (sprawdź docker compose config | grep SECRET).
  • Odpowiedź pojawia się dopiero w całości albo brakuje w niej słów. nginx buforuje odpowiedź – sprawdź proxy_buffering off.
  • Interfejs pokazuje błąd połączenia, czat się nie odświeża. Brak nagłówków Upgrade/Connection albo inny adres w CORS_ALLOW_ORIGIN niż ten w pasku przeglądarki.
  • Nie widać modeli Ollamy. Zły OLLAMA_BASE_URL (w tym układzie http://ollama:11434) albo w Ollamie nie ma jeszcze żadnego modelu: docker compose exec ollama ollama pull qwen3:8b.
  • Zmiana zmiennej w compose.yaml nic nie zmienia. To ustawienie z grupy zapisywanych w bazie przy pierwszym starcie – zmień je w panelu administratora.

Źródła

Sprawdzone 12.11.2026:

Powiązane poradniki