n8n: correggere il fuso orario dei trigger Schedule
Un n8n self-hosted legge tre fusi orari: scopri perché il trigger Schedule può scattare ore dopo e come configurare TZ, GENERIC_TIMEZONE e workflow.
Perché il trigger Schedule di n8n viene eseguito all'ora sbagliata
Un trigger Schedule di n8n viene eseguito all'ora sbagliata perché n8n legge il fuso orario da tre posizioni separate e la correzione di una sola posizione risolve soltanto una parte del problema. Le tre posizioni sono la variabile TZ del container, il valore predefinito dell'istanza GENERIC_TIMEZONE e un fuso orario impostato all'interno di un singolo workflow. Impostale tutte una volta sola e ogni pianificazione creata in seguito verrà eseguita all'ora prevista.
Correggi prima un'ipotesi comune. Una nuova installazione self-hosted di n8n non pianifica le esecuzioni in UTC (tempo coordinato universale). L'orologio del container è impostato su UTC perché l'immagine ufficiale non definisce alcun valore TZ. La pianificazione è un livello separato e il valore predefinito documentato da n8n per GENERIC_TIMEZONE è America/New_York (ad agosto 2026). Di conseguenza, un'istanza non modificata esegue i relativi Schedule Trigger sull'ora di New York. Per questo lo scarto segnalato raramente corrisponde alla distanza effettiva dell'utente da UTC. Un amministratore a Berlino che imposta le 06:00 ottiene le 12:00 nell'ora locale, oppure le 11:00 durante le settimane di marzo in cui gli Stati Uniti sono già passati all'ora legale e l'Europa non lo ha ancora fatto.
I tre livelli di fuso orario e quale ha la precedenza
TZ è il fuso orario del sistema operativo all’interno del container. La documentazione di n8n lo descrive come la variabile che imposta il fuso orario di sistema, determinando il risultato di script e comandi come date. Stabilisce cosa restituisce date all’interno del container, quale timestamp viene scritto in una riga di log del container, cosa restituisce new Date() in un nodo Code e quale fuso orario rileva qualsiasi script shell eseguito nel container. Non influisce sull’orario di esecuzione di un Schedule Trigger.
GENERIC_TIMEZONE è il fuso orario dell’istanza n8n. La documentazione lo definisce come il fuso orario dell’istanza n8n e specifica che è importante per i nodi di pianificazione, ad esempio Cron. In questo contesto, Cron indica la sintassi standard per la pianificazione basata sull’orario, esposta da n8n come opzione Custom (Cron) nel Schedule Trigger.
Il fuso orario del workflow viene impostato per ogni workflow. Apri il workflow nell’area di lavoro, seleziona i tre punti nell’angolo superiore destro, seleziona Settings, quindi modifica il valore Timezone. Questa impostazione sostituisce GENERIC_TIMEZONE per quel workflow.
Per un Schedule Trigger l’ordine è fisso. n8n usa il fuso orario del workflow, se configurato; in caso contrario usa il fuso orario dell’istanza indicato da GENERIC_TIMEZONE; se anche questo non è disponibile, usa il valore predefinito integrato America/New_York. TZ non viene consultato in nessuna fase di questa decisione.
Per le date all’interno dei nodi, la risposta dipende dall’orologio utilizzato dal codice. Luxon, la libreria per le date utilizzata dalle espressioni n8n, usa il fuso orario n8n. Di conseguenza, $now e $today seguono lo stesso ordine workflow-istanza del trigger. Il normale new Date() JavaScript in un nodo Code interroga il sistema operativo, quindi segue TZ. Questa differenza è la causa principale della confusione: il trigger può essere corretto, mentre ogni timestamp scritto dal workflow può risultare spostato di diverse ore.
Impostare tutti e tre nel file Compose
Inserire TZ e GENERIC_TIMEZONE uno accanto all'altro nel file, per evitare che venga impostato uno solo dei due. Il frammento seguente mostra la parte del servizio relativa al fuso orario in una configurazione funzionante. Il resto del file, il reverse proxy e il certificato sono descritti in un'istanza self-hosted di n8n su un VPS dietro 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:Applicare la modifica con docker compose up -d, non con docker compose restart. Un riavvio avvia di nuovo lo stesso container usando l'ambiente con cui è stato creato. Di conseguenza, il file cambia ma il processo in esecuzione no. up -d rileva l'ambiente modificato e ricrea il container. Se si mantengono questi valori in un file env invece di inserirli direttamente, si applica la stessa regola della ricreazione. La guida gestione dei file env e dei secret in Compose spiega da quale percorso viene letto il file.
In Region/City usare il nome di una zona IANA (Internet Assigned Numbers Authority), ad esempio Europe/Berlin o America/Sao_Paulo. Questi nomi includono le regole dell'ora legale del luogo indicato, quindi l'offset cambia quando cambia l'ora locale. Un nome con offset fisso come Etc/GMT+5 non cambia con le stagioni e il segno è invertito rispetto a quello che si potrebbe presumere. Eseguire LC_ALL=C TZ=Etc/GMT+5 date +%z per ottenere -0500. Evitare questi nomi.
Perché impostarne uno solo risolve il problema soltanto a metà
Imposta solo GENERIC_TIMEZONE: Schedule Trigger si attiva all'ora prevista, mentre tutto ciò che legge il sistema operativo resta su UTC. Un nodo Code che chiama new Date().toString() restituisce una stringa UTC, le righe del log del container riportano timestamp UTC e qualsiasi nome file creato in base all'orologio di sistema passa al giorno successivo alla mezzanotte sbagliata.
Imposta solo TZ e accade il contrario. docker compose exec n8n date stampa l'ora locale, dando l'impressione che la configurazione sia corretta, mentre Schedule Trigger continua a usare America/New_York e si attiva sei ore prima o dopo l'ora richiesta. Questa configurazione fa perdere più tempo, perché il primo controllo che eseguono quasi tutti ora ha esito positivo.
Imposta il fuso orario di un workflow e modifica successivamente GENERIC_TIMEZONE: quel workflow ignora la modifica. Prevale il valore configurato nel workflow e continua a prevalere finché qualcuno non apre le impostazioni del workflow. Se un workflow viene eseguito a un'ora insolita mentre quelli vicini funzionano correttamente, la causa è quasi sempre questa.
Controllare gli orologi invece di procedere per ipotesi
Confrontare direttamente l'host e il container.
date
docker compose exec n8n date
docker compose exec n8n printenv TZ GENERIC_TIMEZONEI primi due comandi devono mostrare la stessa ora corrente dopo aver impostato TZ. printenv stampa una riga per ogni variabile presente: due righe di output indicano che entrambe sono impostate, mentre una riga indica lo stato parzialmente corretto.
A questo punto interrogare direttamente n8n dall'interno di un workflow, perché una shell del container non può indicare il fuso orario configurato a livello di workflow. Aggiungere un nodo Code al workflow che presenta il problema ed eseguirlo una volta con Execute Workflow.
return [
{
json: {
n8n_time: $now.toISO(),
n8n_zone: $now.zoneName,
system_time: new Date().toString(),
},
},
];n8n_zone è il fuso orario utilizzato dallo Schedule Trigger di questo workflow, già risolto secondo l'ordine workflow e poi istanza; risponde quindi direttamente alla domanda. system_time contiene il fuso orario del container, ricavato da TZ. Eseguire il controllo nel workflow che presenta il problema, non in uno nuovo, perché l'impostazione a livello di workflow viene mantenuta dal workflow. Se i due valori non coincidono, il problema è stato individuato senza aprire un solo file di configurazione.
Espressioni cron nel nodo Schedule Trigger
Schedule Trigger offre intervalli fissi da pochi secondi a diversi mesi, oltre all'opzione Custom (Cron) per gli intervalli non coperti. L'espressione cron viene interpretata nel fuso orario effettivo del workflow, quindi 0 6 * * * indica le 06:00 in quel fuso e non le 06:00 UTC. Un'espressione a cinque campi copiata da crontab guru può essere incollata senza modifiche. n8n accetta anche un campo opzionale per i secondi, che nella tabella dei campi della documentazione è il primo: secondi, minuti, ore, giorno del mese, mese, giorno della settimana.
Non codificare manualmente l'offset. Scrivere 0 4 * * * su un'istanza UTC per eseguire l'attività alle 06:00 a Berlino è corretto in inverno, ma introduce un errore di un'ora per tutta l'estate, perché Berlino usa UTC+1 in inverno e UTC+2 in estate. Imposta il fuso orario e specifica l'ora locale desiderata.
Cosa comporta l’ora legale per un job pianificato alle 02:30
Un’ora locale non identifica un istante garantito. Due volte all’anno un’ora scompare e un’altra si ripete, quindi ogni job pianificato in queste fasce è interessato dal cambio. È possibile osservare il comportamento con date su qualsiasi sistema Linux, senza coinvolgere n8n.
LC_ALL=C TZ=Europe/Berlin date -d '2027-03-28 02:30'date: invalid date '2027-03-28 02:30'Il comando non contiene un errore di battitura. Il 2027-03-28 l’orologio di Berlino passa direttamente dalle 02:00 alle 03:00, quindi quel giorno le 02:30 locali non esistono e date non riesce a convertirle in un istante. Un job ancorato alle 02:30 locali non ha alcun momento in cui essere eseguito. Gli orari adiacenti non presentano problemi: date -d '2027-03-28 01:30' viene risolto come CET e date -d '2027-03-28 03:30' come CEST.
Il passaggio autunnale è il caso inverso. Il 2027-10-31 l’orologio di Berlino torna indietro dalle 03:00 alle 02:00, quindi le 02:30 si verificano due volte.
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
1824946200Si tratta di due istanti diversi, entrambi indicati come 02:30 locali, separati da 3600 secondi. Un job pianificato in quell’orario può essere eseguito due volte oppure una sola volta a un’ora non prevista, e nessuno dei due risultati è accettabile per un’elaborazione di fatturazione o per la rotazione dei backup. Spostare la pianificazione fuori da questa fascia. Nella maggior parte dei fusi orari europei e nordamericani, la fascia a rischio va dalle 00:00 alle 03:00 locali.
Pianifica l'infrastruttura in UTC e mostra l'ora locale alle persone
La risposta standard separa i due compiti di un fuso orario. Le macchine hanno bisogno di un intervallo stabile. Le persone hanno bisogno di un'ora leggibile.
- Per le attività che nessuno controlla, imposta il fuso orario del workflow su UTC. I backup, il pre-caricamento della cache, l'inoltro dei log e la generazione dei report rientrano in questa categoria. In UTC, l'intervallo tra due esecuzioni corrisponde esattamente a quello configurato, in ogni giorno dell'anno, perché UTC non applica l'ora legale.
- Per le attività che una persona deve leggere, mantieni la pianificazione in UTC e converti l'ora al momento della visualizzazione. Una sola espressione è sufficiente:
{{ $now.setZone('Europe/Berlin').toFormat('yyyy-MM-dd HH:mm') }}inserisce l'ora locale nel corpo del messaggio, mentre il trigger rimane stabile.
La stessa separazione si applica anche al di fuori di n8n. Quando una parte dell'automazione viene eseguita come servizio e timer systemd sul VPS, la relativa riga OnCalendar viene interpretata nel fuso orario del sistema, che costituisce un quarto orologio con una propria configurazione. Mantenere tutti gli scheduler in UTC lascia una sola regola da ricordare invece di quattro. Questo è importante anche per tutto ciò che riassume un periodo, perché un workflow con agente AI n8n a cui vengono richiesti i dati di ieri userà in modo silenzioso 24 ore diverse a seconda del fuso orario utilizzato per la risoluzione.
Modalità di errore e output visualizzato
Tutto viene eseguito con circa sei ore di scarto. GENERIC_TIMEZONE non è mai stato impostato, quindi viene applicato il valore predefinito integrato America/New_York. docker compose exec n8n printenv GENERIC_TIMEZONE non restituisce alcun output. Impostare il valore e ricreare il container.
Hai modificato il file Compose, ma non è cambiato nulla. Hai eseguito docker compose restart, quindi il container ha mantenuto il proprio ambiente originale. Esegui docker compose up -d, quindi verifica con docker compose exec n8n printenv TZ.
Il trigger è corretto, ma i timestamp sono errati. È impostato solo GENERIC_TIMEZONE. Un new Date() in un nodo Code continua a leggere il fuso orario UTC dal sistema operativo. Imposta TZ sullo stesso valore e ricrea il container.
Un workflow ignora l'impostazione dell'istanza. Quel workflow contiene un proprio fuso orario nelle impostazioni, che ha la precedenza su GENERIC_TIMEZONE. Apri il canvas, seleziona i tre puntini, quindi Settings e Timezone.
Un job giornaliero è stato eseguito due volte oppure ha saltato un giorno, una volta durante quest'anno. L'ora pianificata rientra in una transizione dell'ora legale. Sposta l'ora oppure imposta quel workflow su UTC.
FAQ
Perché il trigger di pianificazione di n8n viene eseguito all'ora sbagliata?
Il workflow usa un fuso orario diverso da quello previsto. n8n utilizza il fuso orario del workflow, se configurato; in caso contrario usa quello dell'istanza indicato da GENERIC_TIMEZONE; se anche questo non è configurato, usa il valore predefinito integrato America/New_York. In un'istanza self-hosted in cui nessuno ha impostato GENERIC_TIMEZONE, le pianificazioni usano l'ora di New York, non UTC. Per questo lo scarto raramente corrisponde alla propria differenza rispetto a UTC. Esegui docker compose exec n8n printenv GENERIC_TIMEZONE. Nessun output significa che il valore non è mai stato impostato.
Qual è la differenza tra TZ e GENERIC_TIMEZONE in n8n?
TZ è il fuso orario del sistema operativo all'interno del container. Determina il risultato di date nel container, i timestamp presenti nelle righe dei log del container, il risultato di new Date() in un nodo Code e ciò che rileva qualsiasi script eseguito al suo interno. GENERIC_TIMEZONE è il fuso orario dell'istanza n8n e viene usato dai nodi di pianificazione e dalle espressioni Luxon come $now. Se imposti una sola delle due variabili, puoi ottenere un trigger corretto con timestamp errati, oppure timestamp corretti con un trigger eseguito a distanza di ore. Imposta entrambe sullo stesso valore.
Devo impostare il fuso orario del workflow o GENERIC_TIMEZONE?
Imposta GENERIC_TIMEZONE come valore predefinito per l'intera istanza e usa l'impostazione specifica del workflow solo quando un workflow appartiene effettivamente a un altro fuso orario. Il valore del workflow ha precedenza su quello dell'istanza e non viene aggiornato dalle modifiche successive a GENERIC_TIMEZONE. Un override specifico del workflow dimenticato può quindi essere difficile da individuare anche mesi dopo.
Che cosa accade a un job pianificato alle 02:30 quando cambia l'ora?
Quell'ora locale può scomparire oppure verificarsi due volte. LC_ALL=C TZ=Europe/Berlin date -d '2027-03-28 02:30' restituisce date: invalid date '2027-03-28 02:30', perché quel giorno l'orologio di Berlino passa dalle 02:00 alle 03:00. Il 2027-10-31 la stessa ora indicata dall'orologio corrisponde a due istanti distanti un'ora. Evita di pianificare attività nella fascia locale compresa tra 00:00 e 03:00 oppure imposta il workflow su UTC e converti l'ora locale solo nel punto in cui deve essere letta da una persona.