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

n8n harmonogram uruchamia się o złej godzinie

Wyzwalacz harmonogramu n8n działa nieprawidłowo przez rozbieżności w konfiguracji stref czasowych. Dowiedz się, jak poprawnie ustawić zmienne TZ, GENERIC_TIMEZONE oraz strefę workflow.

Dlaczego wyzwalacz harmonogramu n8n uruchamia się o niewłaściwej godzinie

Wyzwalacz harmonogramu n8n uruchamia się o niewłaściwej godzinie, ponieważ n8n odczytuje strefę czasową z trzech różnych miejsc, a poprawienie tylko jednego z nich rozwiązuje problem jedynie częściowo. Tymi trzema miejscami są zmienna TZ samego kontenera, domyślna strefa instancji GENERIC_TIMEZONE oraz strefa czasowa ustawiona wewnątrz poszczególnego workflow. Skonfiguruj wszystkie trzy, a każdy kolejny harmonogram będzie działał zgodnie z oczekiwaniami.

Najpierw zweryfikuj błędne założenie. Świeża instancja n8n hostowana samodzielnie nie planuje zadań w czasie UTC (uniwersalny czas koordynowany). Zegar kontenera działa w UTC, ponieważ oficjalny obraz nie ustawia żadnej wartości TZ. Harmonogramowanie to osobna warstwa, a udokumentowana wartość domyślna n8n dla GENERIC_TIMEZONE to America/New_York (stan na sierpień 2026), więc nietknięta instancja uruchamia wyzwalacze harmonogramu według czasu nowojorskiego. To dlatego zgłaszane przez użytkowników przesunięcia rzadko odpowiadają ich faktycznej odległości od UTC. Właściciel w Berlinie, który ustawia godzinę 06:00, otrzymuje uruchomienie o 12:00 czasu lokalnego, a w tygodniach marca, gdy Stany Zjednoczone przeszły już na czas letni, a Europa jeszcze nie – o 11:00.

Trzy warstwy stref czasowych i hierarchia ich ważności

TZ to strefa czasowa systemu operacyjnego wewnątrz kontenera. Dokumentacja n8n opisuje ją jako zmienną ustawiającą strefę czasową systemu, która kontroluje wartości zwracane przez skrypty i polecenia, takie jak date. Decyduje ona o tym, co wyświetla date wewnątrz kontenera, jaki znacznik czasu trafia do logów kontenera, co zwraca new Date() w węźle Code oraz co widzą uruchamiane tam skrypty powłoki. Nie ma ona wpływu na moment wyzwolenia Schedule Trigger.

GENERIC_TIMEZONE to strefa czasowa instancji n8n. Dokumentacja określa ją jako strefę czasową instancji n8n i wskazuje na jej znaczenie dla węzłów harmonogramu, takich jak Cron. Cron oznacza tutaj standardową składnię harmonogramowania opartego na czasie, a n8n udostępnia ją jako opcję Custom (Cron) w węźle Schedule Trigger.

Strefa czasowa przepływu pracy (workflow) jest ustawiana indywidualnie dla każdego procesu. Należy otworzyć przepływ pracy na kanwie, wybrać trzy kropki w prawym górnym rogu, przejść do Settings, a następnie zmienić wartość Timezone. Ustawienie to nadpisuje GENERIC_TIMEZONE dla danego przepływu pracy.

W przypadku Schedule Trigger kolejność jest ustalona. n8n używa strefy czasowej przepływu pracy, jeśli została zdefiniowana, w przeciwnym razie strefy czasowej instancji z GENERIC_TIMEZONE, a w ostateczności wbudowanej wartości domyślnej America/New_York. TZ nie jest brana pod uwagę na żadnym etapie tego procesu decyzyjnego.

W przypadku dat wewnątrz węzłów odpowiedź zależy od tego, o który zegar pyta kod. Luxon, biblioteka obsługująca daty w wyrażeniach n8n, korzysta ze strefy czasowej n8n, więc $now oraz $today podlegają tej samej kolejności (przepływ pracy, a następnie instancja), co wyzwalacz. Zwykły JavaScript new Date() w węźle Code odwołuje się do systemu operacyjnego, więc podąża za TZ. Ten podział jest głównym źródłem nieporozumień: wyzwalacz może działać poprawnie, podczas gdy każdy znacznik czasu zapisywany przez przepływ pracy jest przesunięty o kilka godzin.

Ustawienie wszystkich trzech parametrów w pliku Compose

Umieść TZ oraz GENERIC_TIMEZONE obok siebie w pliku, aby uniknąć sytuacji, w której jeden z nich zostanie ustawiony, a drugi pominięty. Poniższy fragment przedstawia część działającej usługi odpowiedzialną za strefę czasową. Pozostała część pliku, reverse proxy oraz certyfikat, pochodzą z własnej instancji n8n na VPS za HTTPS.

services:
  n8n:
    image: docker.n8n.io/n8nio/n8n
    restart: unless-stopped
    ports:
      - "127.0.0.1:5678:5678"
    environment:
      - GENERIC_TIMEZONE=Europe/Berlin
      - TZ=Europe/Berlin
      - N8N_RUNNERS_ENABLED=true
      - N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true
    volumes:
      - n8n_data:/home/node/.n8n

volumes:
  n8n_data:

Zastosuj zmiany za pomocą docker compose up -d, a nie docker compose restart. Restart uruchamia ten sam kontener z tym samym środowiskiem, z którym został utworzony, więc zmiany w pliku nie wpływają na działający proces. up -d wykrywa zmienione środowisko i odtwarza kontener. Jeśli wartości te są przechowywane w pliku env zamiast bezpośrednio w pliku, obowiązuje ta sama zasada odtwarzania, a przewodnik obsługa plików env i sekretów w Compose wyjaśnia, skąd taki plik jest wczytywany.

Użyj nazwy strefy IANA (Internet Assigned Numbers Authority) w formacie Region/City, na przykład Europe/Berlin lub America/Sao_Paulo. Nazwy te zawierają reguły czasu letniego dla danego miejsca, dzięki czemu przesunięcie zmienia się wraz z przestawieniem zegarów. Nazwa o stałym przesunięciu, taka jak Etc/GMT+5, nigdy nie zmienia się wraz z porami roku, a jej znak jest odwrotny do oczekiwanego. Uruchom LC_ALL=C TZ=Etc/GMT+5 date +%z, a otrzymasz -0500. Unikaj stosowania takich nazw.

Dlaczego ustawienie tylko jednego z nich rozwiązuje problem połowicznie

Ustawienie samego GENERIC_TIMEZONE sprawia, że Schedule Trigger uruchamia się o wybranej godzinie, podczas gdy wszystkie procesy odczytujące czas z systemu operacyjnego nadal działają w UTC. Węzeł Code wywołujący new Date().toString() zwraca ciąg znaków w UTC, wpisy w dziennikach kontenerów są znaczone czasem UTC, a nazwy plików tworzone na podstawie zegara systemowego zmieniają datę o niewłaściwej północy.

Ustawienie samego TZ powoduje sytuację odwrotną. docker compose exec n8n date wyświetla czas lokalny, co wygląda na poprawne działanie, podczas gdy Schedule Trigger nadal korzysta z America/New_York i uruchamia się z przesunięciem sześciu godzin względem oczekiwanej pory. Jest to wariant generujący najwięcej strat czasu, ponieważ test, który większość osób wykonuje jako pierwszy, to właśnie ten, który teraz przechodzi pomyślnie.

Ustawienie strefy czasowej dla workflow, a następnie późniejsza zmiana GENERIC_TIMEZONE, powoduje, że workflow ignoruje tę zmianę. Wartość zdefiniowana w workflow ma priorytet i zachowuje go, dopóki ktoś nie otworzy ustawień tego konkretnego workflow. Jeśli jeden workflow uruchamia się o nietypowej godzinie, podczas gdy pozostałe działają poprawnie, niemal zawsze przyczyną jest właśnie ten mechanizm.

Sprawdź zegary zamiast zgadywać

Porównaj bezpośrednio hosta oraz kontener.

date
docker compose exec n8n date
docker compose exec n8n printenv TZ GENERIC_TIMEZONE

Pierwsze dwa polecenia powinny wyświetlić ten sam czas zegarowy, gdy zmienna TZ zostanie ustawiona. Polecenie printenv wypisuje jedną linię dla każdej istniejącej zmiennej, więc dwa wiersze wyjścia oznaczają, że obie są ustawione, a jeden wiersz oznacza stan częściowo naprawiony.

Teraz zapytaj samą aplikację n8n z poziomu workflow, ponieważ powłoka kontenera nie informuje o strefie czasowej ustawionej na poziomie workflow. Dodaj węzeł Code do workflow, który działa nieprawidłowo i uruchom go raz za pomocą Execute Workflow.

return [
  {
    json: {
      n8n_time: $now.toISO(),
      n8n_zone: $now.zoneName,
      system_time: new Date().toString(),
    },
  },
];

n8n_zone to strefa, której użyje Schedule Trigger tego workflow, już rozwiązana zgodnie z kolejnością workflow-a-instancja, więc odpowiada bezpośrednio na pytanie. system_time przenosi strefę samego kontenera z TZ. Uruchom to w workflow, który działa nieprawidłowo, a nie w nowym, ponieważ ustawienie na poziomie workflow jest z nim powiązane. Jeśli te dwie wartości są różne, problem został zidentyfikowany bez otwierania ani jednego pliku konfiguracyjnego.

Wyrażenia cron w węźle Schedule Trigger

Węzeł Schedule Trigger oferuje stałe interwały od sekund do miesięcy oraz opcję Custom (Cron) dla pozostałych przypadków. Wyrażenie cron jest interpretowane w strefie czasowej przypisanej do workflow, więc 0 6 * * * oznacza godzinę 06:00 w tej strefie, a nie 06:00 UTC. Pięciopolowe wyrażenie skopiowane z serwisu crontab guru działa bez zmian. n8n obsługuje również opcjonalne pole sekund, które w tabeli pól w dokumentacji znajduje się na pierwszej pozycji: sekunda, minuta, godzina, dzień miesiąca, miesiąc, dzień tygodnia.

Nigdy nie należy ręcznie obliczać przesunięcia czasowego. Wpisanie 0 4 * * * na instancji działającej w UTC w celu uzyskania godziny 06:00 w Berlinie jest poprawne zimą, ale błędne przez całe lato, ponieważ Berlin stosuje czas UTC+1 zimą i UTC+2 latem. Należy ustawić odpowiednią strefę czasową i wpisać lokalną godzinę, która jest faktycznie wymagana.

Wpływ czasu letniego na zadania zaplanowane na 02:30

Lokalny czas zegarowy nie jest gwarantowanym punktem w czasie. Dwa razy w roku jedna godzina znika, a jedna powtarza się, co wpływa na każde zadanie zaplanowane w tych godzinach. Można zaobserwować to zjawisko za pomocą date na dowolnym systemie Linux, bez udziału n8n.

LC_ALL=C TZ=Europe/Berlin date -d '2027-03-28 02:30'
date: invalid date '2027-03-28 02:30'

To nie jest błąd w poleceniu. W dniu 2027-03-28 zegar w Berlinie przeskakuje bezpośrednio z 02:00 na 03:00, więc godzina 02:30 czasu lokalnego tego dnia nie istnieje, a date odmawia przekształcenia jej w konkretny moment. Zadanie przypisane do godziny 02:30 nie ma punktu w czasie, w którym mogłoby zostać uruchomione. Sąsiednie godziny działają poprawnie: date -d '2027-03-28 01:30' jest rozpoznawane jako CET, a date -d '2027-03-28 03:30' jako CEST.

Zmiana czasu na jesienny jest lustrzanym odbiciem. W dniu 2027-10-31 zegar w Berlinie cofa się z 03:00 na 02:00, więc godzina 02:30 występuje dwukrotnie.

LC_ALL=C TZ=Europe/Berlin date -d '2027-10-31 02:30 CEST' '+%s'
LC_ALL=C TZ=Europe/Berlin date -d '2027-10-31 02:30 CET' '+%s'
1824942600
1824946200

Dwa różne momenty w czasie, oba nazywane 02:30 czasu lokalnego, oddzielone 3600 sekundami. Zadanie przypisane do tej godziny albo uruchomi się dwukrotnie, albo raz w godzinie, której nikt nie wybrał, a żaden z tych rezultatów nie jest pożądany w przypadku rozliczeń czy rotacji kopii zapasowych. Należy przenieść harmonogram poza to okno czasowe. W większości stref europejskich i północnoamerykańskich ryzykowny przedział to 00:00 do 03:00 czasu lokalnego.

Planowanie infrastruktury w UTC i wyświetlanie czasu lokalnego użytkownikom

Standardowe rozwiązanie rozdziela dwie funkcje strefy czasowej. Maszyny wymagają stabilnego interwału. Ludzie potrzebują czytelnej godziny.

  • W przypadku zadań, których nikt nie nadzoruje, należy ustawić strefę czasową przepływu pracy na UTC. Dotyczy to kopii zapasowych, rozgrzewania pamięci podręcznej, przesyłania logów oraz generowania raportów. W UTC odstęp między dwoma uruchomieniami jest zawsze dokładnie taki, jaki zdefiniowano, każdego dnia roku, ponieważ UTC nie stosuje czasu letniego.
  • W przypadku zadań, które są odczytywane przez ludzi, należy zachować harmonogram w UTC i dokonywać konwersji w momencie wyświetlania. Wystarczy jedno wyrażenie: {{ $now.setZone('Europe/Berlin').toFormat('yyyy-MM-dd HH:mm') }} umieszcza czas lokalny w treści wiadomości, podczas gdy wyzwalacz pozostaje stabilny.

Ten sam podział ma zastosowanie poza n8n. Gdy część automatyzacji działa jako usługa systemd i timer na VPS, jej linia OnCalendar jest odczytywana zgodnie ze strefą czasową systemu, która stanowi czwarty zegar z własnymi ustawieniami. Utrzymywanie wszystkich harmonogramów w UTC pozwala zapamiętać jedną zasadę zamiast czterech. Ma to również znaczenie dla każdego zadania podsumowującego dany okres, ponieważ agent AI w n8n zapytany o dane z wczoraj, po cichu użyje różnych 24-godzinnych przedziałów w zależności od tego, która strefa czasowa została rozpoznana.

Tryby awarii i komunikaty wyjściowe

Wszystkie zadania uruchamiają się z sześciogodzinnym opóźnieniem. Zmienna GENERIC_TIMEZONE nie została ustawiona, więc zastosowano wbudowaną wartość domyślną America/New_York. Polecenie docker compose exec n8n printenv GENERIC_TIMEZONE nie zwraca żadnych danych. Należy ustawić zmienną, a następnie odtworzyć kontener.

Edycja pliku Compose nie przyniosła zmian. Uruchomiono docker compose restart, więc kontener zachował pierwotne środowisko. Należy wykonać docker compose up -d, a następnie potwierdzić stan za pomocą docker compose exec n8n printenv TZ.

Wyzwalacz działa poprawnie, ale znaczniki czasu są błędne. Ustawiono tylko GENERIC_TIMEZONE. Węzeł new Date() w kodzie nadal odczytuje czas UTC z systemu operacyjnego. Należy ustawić TZ na tę samą wartość i odtworzyć kontener.

Jeden z przepływów pracy ignoruje ustawienia instancji. Przepływ ten posiada własną strefę czasową w ustawieniach, która ma pierwszeństwo przed GENERIC_TIMEZONE. Należy otworzyć obszar roboczy, wybrać trzy kropki, przejść do Settings, a następnie Timezone.

Zadanie codzienne uruchomiło się dwukrotnie lub pominęło dzień w tym roku. Zaplanowana godzina przypada na moment zmiany czasu z letniego na zimowy lub odwrotnie. Należy zmienić godzinę uruchomienia lub przenieść przepływ pracy na czas UTC.

FAQ

Dlaczego wyzwalacz harmonogramu w n8n uruchamia się o niewłaściwej godzinie?

Przepływ pracy (workflow) rozpoznaje inną strefę czasową, niż zakładasz. n8n wybiera strefę czasową przepływu, jeśli została ona zdefiniowana; w przeciwnym razie używa strefy czasowej instancji z GENERIC_TIMEZONE, a jeśli i ta nie istnieje – wbudowanej wartości domyślnej America/New_York. W instancji hostowanej samodzielnie, gdzie nie ustawiono GENERIC_TIMEZONE, harmonogramy działają według czasu nowojorskiego, a nie UTC, dlatego przesunięcie rzadko odpowiada Twojej odległości od UTC. Uruchom docker compose exec n8n printenv GENERIC_TIMEZONE. Brak wyniku oznacza, że zmienna nigdy nie została ustawiona.

Jaka jest różnica między TZ a GENERIC_TIMEZONE w n8n?

TZ to strefa czasowa systemu operacyjnego wewnątrz kontenera. Kontroluje ona, co zwraca date wewnątrz kontenera, jakie znaczniki czasu pojawiają się w liniach dziennika kontenera, co zwraca new Date() w węźle Code oraz co widzą uruchamiane tam skrypty. GENERIC_TIMEZONE to strefa czasowa instancji n8n, z której korzystają węzły harmonogramu oraz wyrażenia Luxon, takie jak $now. Ustawienie tylko jednej z nich powoduje albo poprawny wyzwalacz przy błędnych znacznikach czasu, albo poprawne znaczniki czasu przy wyzwalaczu uruchamiającym się z wielogodzinnym opóźnieniem. Ustaw obie zmienne na tę samą wartość.

Czy powinienem ustawić strefę czasową dla przepływu pracy czy GENERIC_TIMEZONE?

Ustaw GENERIC_TIMEZONE jako wartość domyślną dla całej instancji, a ustawienia dla poszczególnych przepływów stosuj tylko wtedy, gdy dany przepływ faktycznie przynależy do innej strefy. Wartość przepływu ma pierwszeństwo przed wartością instancji i nie podlega późniejszym zmianom w GENERIC_TIMEZONE, dlatego zapomniane nadpisanie w konkretnym przepływie jest trudne do zdiagnozowania po kilku miesiącach.

Co dzieje się z zadaniem zaplanowanym na 02:30 podczas zmiany czasu?

Ten czas lokalny albo znika, albo występuje dwukrotnie. LC_ALL=C TZ=Europe/Berlin date -d '2027-03-28 02:30' zwraca date: invalid date '2027-03-28 02:30', ponieważ zegar w Berlinie przeskakuje tego dnia z 02:00 na 03:00. W dniu 2027-10-31 ten sam odczyt zegara odpowiada dwóm momentom oddalonym od siebie o godzinę. Unikaj planowania zadań w przedziale od 00:00 do 03:00 czasu lokalnego lub ustaw przepływ pracy na UTC i konwertuj na czas lokalny tylko tam, gdzie jest on odczytywany przez człowieka.