SSD Nodes Learn 🎉 VPS od $5.50/mies.
Przewodniki Matt ConnorAutor: Matt Connor

n8n przechodzi w tryb offline na VPS – jak naprawić?

n8n zgłasza błąd połączenia? Sprawdź, czy to awaria websocket, restart kontenera, błąd OOM (Out of Memory) czy zatrzymany harmonogram. Poznaj skuteczne metody diagnostyki.

Dlaczego n8n przechodzi w tryb offline: cztery awarie, jeden objaw

„n8n przechodzi w tryb offline” to zdanie opisujące cztery różne awarie, z których każda wymaga innego rozwiązania. Edytor wyświetla komunikat o utracie połączenia, mimo że kontener działa poprawnie. Kontener restartuje się samoczynnie. Jądro systemu zabija proces Node.js z powodu zbyt dużego zużycia pamięci. Albo z procesem jest wszystko w porządku, a aktywny workflow po prostu nigdy się nie uruchamia. Zmiana niewłaściwego ustawienia spowoduje stratę całego weekendu na rozwiązywanie problemu, który nie istnieje.

Dlatego przed przystąpieniem do jakiejkolwiek konfiguracji należy ustalić, z którą awarią mamy do czynienia. n8n działa jako pojedynczy proces Node.js, zazwyczaj wewnątrz jednego kontenera Docker, za reverse proxy, które dokonuje terminacji TLS (transport layer security). Każda z tych warstw ulega awarii w inny sposób, a przeglądarka zgłasza je wszystkie za pomocą tego samego komunikatu.

Diagnozowanie w tej kolejności

Uruchom poniższe polecenia na VPS (virtual private server) i odczytaj wartości wyświetlone przez własną maszynę. Nie porównuj ich z liczbami z wątków na forach. Istotne są wartości opisujące Twój serwer, a nie cudzy.

docker ps -a --filter name=n8n
docker logs --tail 200 --timestamps n8n
docker inspect n8n | grep -iE 'Status|Running|RestartCount|OOMKilled|ExitCode'
docker stats --no-stream

Kolumna STATUS z polecenia docker ps -a informuje, jak długo kontener znajduje się w bieżącym stanie. Porównaj to z momentem wystąpienia problemu. Jeśli kontener działał na długo przed pojawieniem się komunikatu o błędzie, oznacza to, że n8n nie uległ awarii. Uszkodzeniu uległo połączenie między przeglądarką a backendem, co dotyczy ścieżki websocket opisanej w następnej sekcji.

RestartCount to liczba restartów kontenera wykonanych przez Docker. Zapisz tę wartość, odczekaj minutę i odczytaj ją ponownie. Rosnąca w czasie obserwacji liczba oznacza pętlę restartów, a wpisy w dzienniku tuż przed każdym restartem zawierają przyczynę błędu.

OOMKilled to flaga logiczna (prawda/fałsz). Wartość true oznacza, że jądro Linux zabiło proces z powodu przekroczenia limitu pamięci – własnego limitu kontenera lub limitu całej maszyny. To pole pozwala odróżnić błąd braku pamięci od innych przyczyn zakończenia procesu, dlatego należy sprawdzić je przed wysuwaniem hipotez.

ExitCode to kod, z jakim kontener zakończył ostatnio pracę. Nie ma potrzeby zapamiętywania znaczenia każdego kodu. Odczytaj swój kod, a następnie sprawdź końcówkę docker logs z tego samego znacznika czasu. Dopiero połączenie końcówki dziennika z flagą braku pamięci pozwala ustalić przyczynę; poleganie na jednym z tych źródeł może prowadzić do błędnych wniosków.

docker stats pokazuje bieżące zużycie pamięci w odniesieniu do obowiązującego limitu. Uruchom to polecenie w drugim terminalu, wywołaj przepływ pracy powodujący błąd i obserwuj zmiany wartości w momencie wystąpienia awarii.


Komunikat o utracie połączenia zazwyczaj wynika z konfiguracji reverse proxy

Edytor n8n utrzymuje jedno długotrwałe połączenie typu push do backendu, aby przesyłać postęp wykonywania zadań na kanwę. Domyślnie połączenie to wykorzystuje protokół WebSocket, co jest wybierane przez N8N_PUSH_BACKEND, a jego domyślna wartość to websocket. Połączenie WebSocket rozpoczyna się jako zwykłe żądanie HTTP zawierające nagłówki Connection: Upgrade oraz Upgrade: websocket. Serwer odpowiada kodem 101 Switching Protocols i od tego momentu obie strony korzystają z tego samego gniazda TCP w obu kierunkach.

Dwie przyczyny powodują przerwanie tego procesu i obie leżą po stronie proxy, a nie n8n. Proxy komunikuje się z backendem za pomocą HTTP/1.0 lub usuwa nagłówki upgrade, przez co proces aktualizacji protokołu nigdy nie następuje, a edytor próbuje łączyć się w nieskończoność. Alternatywnie, aktualizacja przebiega pomyślnie, ale proxy zamyka gniazdo z powodu braku aktywności, ponieważ WebSocket bez przesyłanych komunikatów wygląda jak bezczynne połączenie. W obu przypadkach kontener działa poprawnie. Wyświetlany baner to informacja z przeglądarki o utracie kanału komunikacji.

Przed wprowadzeniem jakichkolwiek zmian należy potwierdzić to w przeglądarce. Otwórz narzędzia deweloperskie, przejdź do zakładki Network, przefiltruj wyniki pod kątem WS i przeładuj edytor. Żądanie push powinno otrzymać status 101 Switching Protocols i pozostać otwarte. Żądanie push, które zwraca zwykły kod statusu lub pojawia się ponownie co kilka sekund, wskazuje na problem z konfiguracją proxy.

Ustawienia nginx utrzymujące połączenie z edytorem

nginx nie przekazuje żądania upgrade, jeśli nie zostanie do tego jawnie zmuszony. proxy_pass domyślnie komunikuje się z backendem w standardzie HTTP/1.0, a Connection oraz Upgrade to nagłówki typu hop-by-hop, które nginx usuwa podczas przetwarzania żądania. Należy je przywrócić. Blok map umieszcza się w kontekście http, a nie wewnątrz server.

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}
server {
    listen 443 ssl;
    http2 on;
    server_name n8n.example.com;

    location / {
        proxy_pass http://127.0.0.1:5678;
        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_read_timeout 3600s;
        proxy_send_timeout 3600s;
        proxy_buffering off;
    }
}

proxy_read_timeout to linia, o której często się zapomina. Jej domyślna wartość wynosi 60 sekund i dotyczy również zestawionego połączenia WebSocket, dlatego karta edytora pozostawiona otwarta na bezczynnej instancji traci połączenie około minutę po ostatniej przesłanej wiadomości. Zwiększenie tej wartości eliminuje komunikat o błędzie pojawiający się po powrocie do nieaktywnej karty.

sudo nginx -t && sudo systemctl reload nginx
sudo nginx -T | grep -iE 'proxy_http_version|upgrade|proxy_read_timeout'

nginx -T wyświetla całą aktywną konfigurację, a nie tylko pojedynczy plik, co pozwala potwierdzić, czy wprowadzone zmiany zostały wczytane. Jeśli konfiguracja znajduje się w pliku, który nie jest uwzględniony przez żadną dyrektywę include, poprawna poprawka nie przyniesie żadnego efektu.

Następnie należy poinformować n8n, że działa za proxy, ponieważ aplikacja buduje adresy URL na podstawie tych wartości.

environment:
  - N8N_HOST=n8n.example.com
  - N8N_PROTOCOL=https
  - N8N_PORT=5678
  - N8N_PROXY_HOPS=1
  - N8N_WEBHOOK_URL=https://n8n.example.com/

N8N_PROXY_HOPS domyślnie przyjmuje wartość 0, co oznacza, że n8n traktuje adres połączenia przychodzącego jako adres klienta i ignoruje X-Forwarded-For. Należy ustawić tę wartość na liczbę serwerów proxy znajdujących się przed kontenerem. Według stanu na sierpień 2026, N8N_WEBHOOK_URL jest aktualną nazwą zmiennej, a starsza nazwa WEBHOOK_URL nadal działa, choć przy starcie generuje ostrzeżenie o wycofaniu wsparcia.

Traefik przekazuje WebSocket, a następnie przerywa połączenie z powodu przekroczenia limitu czasu

Traefik przekazuje żądanie aktualizacji WebSocket bez użycia middleware i dodatkowych etykiet, więc użytkownik Traefik napotykający ten komunikat zazwyczaj doświadcza przekroczenia limitu czasu, a nie braku nagłówka. Parametry konfiguracyjne znajdują się w entryPoint. Według stanu na sierpień 2026 w Traefik v3, idleTimeout domyślnie wynosi 180 sekund, a readTimeout domyślnie 60 sekund.

entryPoints:
  websecure:
    address: ":443"
    transport:
      respondingTimeouts:
        readTimeout: 0
        idleTimeout: 3600s

Caddy obsługuje aktualizację automatycznie w reverse_proxy i nie wymaga w tym celu żadnej dyrektywy. Jeśli zmiana proxy jest niemożliwa, ponieważ zarządza nim inny podmiot, należy przełączyć kanał przesyłania na N8N_PUSH_BACKEND=sse. SSE (server-sent events) to standardowa odpowiedź HTTP utrzymywana jako otwarta, więc działa ona przez proxy odrzucające aktualizacje, choć agresywny limit czasu bezczynności nadal może ją przerwać. Wybór samego proxy to odrębna decyzja, a porównanie nginx, Caddy i Traefik omawia koszty operacyjne każdego z tych rozwiązań.

Gdy kontener rzeczywiście się restartuje

Jeśli RestartCount rośnie, kontener ulega awarii, a Docker go restartuje. Porównaj znaczniki czasu w logach z momentami restartów i sprawdź, co wystąpiło bezpośrednio przed nimi. Cztery przyczyny odpowiadają za niemal wszystkie przypadki: błąd konfiguracji uniemożliwiający start, baza danych niedostępna dla n8n, awaria w trakcie działania oraz przerwanie procesu przez mechanizm OOM Killer.

Zacznij od wolumenu, ponieważ uprawnienia są częstą, choć nieoczywistą przyczyną problemów. Oficjalny obraz działa jako użytkownik bez uprawnień node i przechowuje dane w /home/node/.n8n. Bind mount utworzony przez root nie jest zapisywalny dla tego użytkownika, więc proces kończy się przy każdym starcie, a polityka restartu ukrywa ten fakt w pętli.

docker compose config
docker run --rm -it --entrypoint sh docker.n8n.io/n8nio/n8n -c 'id'
docker exec n8n ls -ld /home/node/.n8n

Wolumen nazwany całkowicie eliminuje ten problem, ponieważ Docker tworzy go z odpowiednimi uprawnieniami. Jeśli musisz użyć bind mount, wykonaj chown katalogu na hoście, przypisując go do numerycznego identyfikatora użytkownika wyświetlonego przez pierwsze polecenie. Warto raz zrozumieć mapowanie uprawnień między hostem a kontenerem, a wyjaśnienie PUID i PGID opisuje, w jaki sposób te obrazy określają, kto ma prawo zapisu do plików.

Zabicie procesu przez mechanizm OOM, które wygląda jak awaria

Istnieją dwa oddzielne limity pamięci dla procesu n8n, a ich przekroczenie skutkuje różnymi zachowaniami. Limit grupy kontrolnej (cgroup) kontenera jest egzekwowany przez jądro systemu: jego przekroczenie powoduje natychmiastowe zabicie procesu bez możliwości zapisu jakichkolwiek danych, a OOMKilled przyjmuje wartość true. Limit sterty V8 jest egzekwowany wewnątrz Node.js: jego przekroczenie powoduje wygenerowanie błędu sterty wraz ze śladem stosu (stack trace) i samodzielne zakończenie pracy przez Node, co sprawia, że OOMKilled przyjmuje wartość false. Z poziomu przeglądarki oba przypadki wyglądają identycznie. W docker inspect różni je tylko jedno pole.

Należy ustawić limit sterty Node poniżej limitu kontenera. Jeśli limit sterty jest wyższy, V8 kontynuuje alokację pamięci nawet po przekroczeniu progu narzuconego przez jądro, przez co mechanizm odśmiecania (garbage collector) nigdy nie osiąga własnego limitu. W efekcie zawsze dochodzi do bardziej drastycznego zakończenia pracy bez pozostawienia dziennika zdarzeń.

services:
  n8n:
    image: docker.n8n.io/n8nio/n8n
    restart: unless-stopped
    environment:
      - NODE_OPTIONS=--max-old-space-size=<MiB, below the container limit>
    deploy:
      resources:
        limits:
          memory: <your container limit>

Obie wartości należy dobrać na podstawie rzeczywistych zasobów VPS, pozostawiając margines dla bazy danych, proxy oraz systemu operacyjnego. docker stats --no-stream wyświetla bieżące zużycie obok aktywnego limitu, co pozwala zweryfikować, czy zdefiniowany limit został poprawnie zastosowany przez Docker. Jak stosowane są limity pamięci w Compose wyjaśnia, który klucz ma pierwszeństwo w przypadku zdefiniowania kilku z nich.

Dane wykonania to zasób, który narasta w tle

Pojedyncze wykonanie przechowuje dane wyjściowe każdego węzła w trakcie trwania procesu, a n8n następnie zapisuje te dane. Wynikają z tego dwie konsekwencje. Szczytowe zużycie pamięci podczas jednego uruchomienia jest wyznaczane przez największą partię danych, która przez nie przechodzi. Zatem workflow przetwarzający dziesięć tysięcy wierszy naraz jest innym programem niż ten sam workflow przetwarzający dwieście wierszy w jednej iteracji. Ponadto zapisana kopia danych rośnie, dopóki nie zostanie usunięta.

Czyszczenie (pruning) rozwiązuje drugi problem. Od sierpnia 2026 domyślne ustawienia mają włączone czyszczenie, z EXECUTIONS_DATA_MAX_AGE ustawionym na 336 godzin (14 dni) oraz EXECUTIONS_DATA_PRUNE_MAX_COUNT na 10000. Są to wartości wystarczające dla małego serwera VPS z bazą SQLite, gdzie jeden plik przechowuje wszystkie dane, a ten sam proces, który obsługuje edytor, musi je jednocześnie odczytywać i zapisywać.

environment:
  - EXECUTIONS_DATA_PRUNE=true
  - EXECUTIONS_DATA_MAX_AGE=72
  - EXECUTIONS_DATA_PRUNE_MAX_COUNT=1000
  - EXECUTIONS_DATA_SAVE_ON_SUCCESS=none
  - EXECUTIONS_DATA_SAVE_MANUAL_EXECUTIONS=false

EXECUTIONS_DATA_SAVE_ON_SUCCESS=none to ustawienie agresywne. Zachowuje ono nieudane wykonania w celach debugowania i usuwa te zakończone sukcesem. Należy podjąć tę decyzję świadomie, ponieważ workflow, który wygenerował błędne dane bez zgłoszenia błędu, nie pozostawi żadnych informacji do analizy. Proces czyszczenia najpierw oznacza wiersze jako usunięte, a usuwa je w późniejszym przebiegu. Ponadto SQLite ponownie wykorzystuje zwolnione strony zamiast zwracać je do systemu, więc rozmiar pliku na dysku nie zmniejszy się w momencie zmiany ustawienia.

Aby ograniczyć szczytowe zużycie zasobów zamiast całkowitej liczby zapisanych danych, należy przesyłać mniej informacji w ramach jednego uruchomienia. Warto dzielić duże zadania na pod-workflowy zwracające niewielkie wyniki do procesu nadrzędnego, stosować grupowanie za pomocą węzła Loop Over Items oraz unikać przechowywania całych zbiorów danych w węźle Code.

Pliki binarne nie powinny być przetwarzane w pamięci RAM

N8N_DEFAULT_BINARY_DATA_MODE domyślnie przyjmuje wartość default, co powoduje przechowywanie danych binarnych w pamięci operacyjnej uruchomionego procesu. Każdy plik pobrany przez węzeł oraz każda kopia przekazana do kolejnego węzła pozostaje w pamięci aż do zakończenia działania procesu. Jeden przepływ pracy pobierający kilka dużych załączników może spowodować przekroczenie limitu pamięci, do którego standardowe operacje na JSON nigdy się nie zbliżają. Dlatego awaria następuje w momencie wykonania konkretnego przepływu pracy, a nie w określonym czasie.

environment:
  - N8N_DEFAULT_BINARY_DATA_MODE=filesystem

Dzięki filesystem dane binarne są zapisywane w lokalizacji N8N_BINARY_DATA_STORAGE_PATH, która domyślnie znajduje się w folderze użytkownika n8n, a tym samym na tym samym wolumenie co pozostałe dane. Przed przełączeniem należy sprawdzić, czy wolumen dysponuje wystarczającą ilością miejsca. Parametr N8N_PAYLOAD_SIZE_MAX określa maksymalny rozmiar przychodzącego ładunku webhooka w MiB (mebibajtach) i domyślnie wynosi 16. Zwiększenie tej wartości pozwala na obsługę większych żądań, co wiąże się z akceptacją wyższego zużycia pamięci.

Wszystkie procesy współdzielące serwer rywalizują o tę samą pamięć RAM. Jeśli błędy OOM (Out of Memory) zaczęły występować po dodaniu kontenera z bazą danych, uruchomienie bazy danych w Dockerze lub na hoście jest kompromisem, na który trzeba się zdecydować.

Polityka restartu i przywracanie działania po restarcie

Kontener bez zdefiniowanej polityki restartu pozostaje wyłączony po zakończeniu pracy oraz po restarcie hosta. Opcja restart: unless-stopped przywraca go w obu przypadkach, jednocześnie respektując kontenery zatrzymane ręcznie. Opcja restart: always restartuje również kontenery zatrzymane celowo, gdy tylko Docker zostanie uruchomiony ponownie.

Aplikacja n8n udostępnia punkt końcowy sprawdzania stanu (health endpoint), określony przez N8N_ENDPOINT_HEALTH, którego domyślna wartość to healthz. Należy sprawdzić go najpierw z poziomu hosta, aby upewnić się, że ścieżka jest poprawna dla danej instancji.

curl -fsS http://127.0.0.1:5678/healthz
docker exec n8n which wget curl
sudo systemctl is-enabled docker

Sam mechanizm healthcheck nie powoduje restartu. Compose oznacza kontener jako niezdrowy (unhealthy) i na tym kończy działanie, dlatego healthcheck wymaga polityki restartu lub zewnętrznego monitora, aby przyniósł jakikolwiek efekt. Artykuły Tworzenie działającego mechanizmu healthcheck oraz Konfiguracja automatycznego startu stosu po restarcie omawiają obie te kwestie.

Przepływ pracy, który nie uruchamia się mimo poprawnego działania n8n

W tej sytuacji nie pojawia się żaden komunikat o błędzie ani restart. Kontener działa, edytor jest dostępny, a oczekiwane uruchomienie nie pojawia się na liście wykonań. Przyczyną jest zazwyczaj jeden z czterech poniższych czynników.

  • Przepływ pracy nie jest aktywny. Wyzwalacz Schedule Trigger działa tylko w ścieżce produkcyjnej, więc testowanie go w edytorze nie powoduje zaplanowania żadnych zadań.
  • Strefa czasowa jest nieprawidłowa. Domyślną wartością GENERIC_TIMEZONE jest America/New_York, więc harmonogram ustawiony na 09:00 uruchomi się o 09:00 w tej strefie, dopóki nie ustawisz GENERIC_TIMEZONE oraz TZ na własną strefę czasową.
  • Przestoje nie są nadrabiane. Wyzwalacze są rejestrowane w momencie startu n8n, więc harmonogram, którego czas wykonania przypadł na okres restartu kontenera, nie zostanie uruchomiony z opóźnieniem. Kolejne uruchomienie nastąpi w najbliższym terminie po starcie.
  • Przepływ pracy został automatycznie dezaktywowany. Opcja N8N_WORKFLOW_AUTODEACTIVATION_ENABLED jest domyślnie wyłączona, a po jej włączeniu przepływ pracy, który wielokrotnie ulega awarii, zostaje wycofany z publikacji. Po tym zdarzeniu wygląda on dokładnie tak, jakby nikt go nigdy nie aktywował.

Otwórz listę wykonań i przefiltruj ją dla danego przepływu pracy. Wpis, który zakończył się niepowodzeniem, oznacza problem z samym przepływem. Brak jakiegokolwiek wpisu oznacza problem z wyzwalaczem, co wskazuje na konieczność sprawdzenia czterech powyższych przyczyn.

Co zmienić w pierwszej kolejności

  1. Zapoznaj się z STATUS, RestartCount oraz OOMKilled dla własnego kontenera przed edycją jakiegokolwiek pliku.
  2. Jeśli kontener nie uległ awarii, popraw nagłówki aktualizacji proxy oraz limit czasu bezczynności (idle timeout).
  3. Jeśli OOMKilled ma wartość true, ustaw świadomie wybrany limit kontenera, umieść górny limit sterty Node poniżej tej wartości i przełącz dane binarne na filesystem.
  4. Jeśli nic nie zadziałało, sprawdź, czy workflow jest aktywny oraz czy strefa czasowa instancji jest zgodna z Twoją.

Większość z tych ustawień konfiguruje się jednorazowo po poprawnym zainstalowaniu oprogramowania. Jeśli dopiero przygotowujesz instalację, przewodnik po n8n w Dockerze z HTTPS stanowi bazę, w której należy zastosować te ustawienia.

FAQ

Dlaczego edytor n8n wyświetla komunikat o utracie połączenia, mimo że kontener działa?

Edytor utrzymuje otwarte połączenie WebSocket w celu przesyłania postępu wykonywania zadań. Jeśli reverse proxy nie przekazuje nagłówków Connection: Upgrade oraz Upgrade: websocket lub nie używa protokołu HTTP/1.1 w komunikacji upstream, proces aktualizacji połączenia nigdy nie kończy się powodzeniem. Przeglądarka próbuje nawiązać połączenie w nieskończoność, podczas gdy n8n działa poprawnie. W nginx wymagane jest użycie proxy_http_version 1.1 wraz z obiema liniami proxy_set_header oraz ustawienie proxy_read_timeout na wartość większą niż domyślne 60 sekund, aby nie przerywać połączenia w nieaktywnych kartach. Należy sprawdzić aktualną konfigurację poleceniem sudo nginx -T, a nie edytowany plik.

Jak odróżnić błąd braku pamięci (OOM kill) od zwykłej awarii?

Należy wykonać docker inspect n8n | grep -iE 'OOMKilled|ExitCode|RestartCount' i sprawdzić flagę OOMKilled. Wartość True oznacza, że jądro systemu przerwało proces z powodu przekroczenia limitu pamięci; w dzienniku kontenera nie będzie żadnych przydatnych informacji, ponieważ proces nie zdążył zapisać danych. Wartość False, połączona z błędem sterty i śladem stosu na końcu docker logs, oznacza, że Node.js osiągnął własny limit sterty V8 i zakończył działanie samodzielnie. Należy ustawić NODE_OPTIONS=--max-old-space-size na wartość niższą niż limit kontenera, aby wymusić drugi typ błędu, który pozostawia ślady w logach.

Czy usuwanie danych wykonania natychmiast zwalnia miejsce na dysku?

Nie. EXECUTIONS_DATA_PRUNE oznacza stare wykonania jako przeznaczone do usunięcia, a późniejszy proces usuwa je zgodnie z harmonogramem ustawionym w EXECUTIONS_DATA_PRUNE_HARD_DELETE_INTERVAL. W przypadku SQLite plik bazy danych ponownie wykorzystuje zwolnione strony zamiast zwracać je do systemu plików, więc rozmiar pliku na dysku pozostaje niezmieniony przez pewien czas po usunięciu wierszy. Należy ustawić EXECUTIONS_DATA_MAX_AGE oraz EXECUTIONS_DATA_PRUNE_MAX_COUNT na wartości odpowiednie dla danego środowiska i sprawdzić zajętość miejsca następnego dnia, a nie natychmiast.

Dlaczego zaplanowany workflow nie uruchomił się podczas restartu n8n?

n8n rejestruje wyzwalacze w momencie uruchomienia procesu i nie wykonuje ponownie zadań, których termin minął podczas przestoju. Pętla restartów powoduje więc brak aktywności, a nie serię zaległych uruchomień; kolejne wykonanie nastąpi w najbliższym terminie po starcie. Jeśli wymagana jest gwarancja wykonania zadań, należy wyzwalać workflow zewnętrznym wywołaniem webhooka, aby logika ponawiania znajdowała się poza n8n.

Czy healthcheck zrestartuje n8n, gdy przestanie odpowiadać?

Nie samodzielnie. Healthcheck w Compose jedynie oznacza kontener jako zdrowy lub niezdrowy. Restartowanie jest zadaniem polityki restartu, więc to restart: unless-stopped przywraca kontener po jego wyjściu oraz po restarcie hosta, o ile usługa Docker jest włączona. Należy to potwierdzić za pomocą sudo systemctl is-enabled docker. Aby reagować konkretnie na stan niezdrowy, wymagany jest zewnętrzny mechanizm monitorujący, który odczytuje status i restartuje usługę.