SSD Nodes Learn 🎉 VPS desde $5.50/mês
Guias Matt ConnorPor Matt Connor

n8n: corrigir fuso horário do Schedule Trigger

O n8n usa TZ, GENERIC_TIMEZONE e o fuso do workflow. Veja por que o Schedule Trigger pode seguir America/New_York e executar horas fora do esperado.

Por que o gatilho de agendamento do n8n é executado à hora errada

Um gatilho de agendamento do n8n é executado à hora errada porque o n8n lê o fuso horário em três locais diferentes, e corrigir apenas um deles resolve somente parte do problema. Esses três locais são a variável TZ do próprio contentor, a predefinição de instância GENERIC_TIMEZONE e um fuso horário definido dentro de um workflow individual. Defina os três uma vez, e todos os agendamentos criados depois serão executados no horário esperado.

Corrija primeiro uma suposição comum. Um n8n self-hosted recém-instalado não agenda em UTC (tempo universal coordenado). O relógio do contentor está em UTC porque a imagem oficial não define TZ. O agendamento é uma camada separada, e a predefinição documentada do n8n para GENERIC_TIMEZONE é America/New_York (em agosto de 2026). Por isso, uma instância sem alterações executa os Schedule Triggers no horário de Nova Iorque. É também por isso que a diferença de horário relatada raramente corresponde à distância do utilizador em relação a UTC. Um administrador em Berlim que define 06:00 obtém 12:00 no horário local e 11:00 durante as semanas de março em que os Estados Unidos já mudaram para o horário de verão, mas a Europa ainda não.

As três camadas de fuso horário e qual delas prevalece

TZ é o fuso horário do sistema operativo dentro do contentor. A documentação do n8n descreve-o como a variável que define o fuso horário do sistema e controla o resultado de scripts e comandos como date. Determina o que date apresenta dentro do contentor, que timestamp é registado numa linha de log do contentor, o que new Date() devolve num nó Code e o que qualquer script de shell executado nesse ambiente deteta. Não afeta o momento em que um Schedule Trigger é executado.

GENERIC_TIMEZONE é o fuso horário da instância do n8n. A documentação chama-lhe o fuso horário da instância do n8n e indica que é importante para nós de agendamento, como Cron. Aqui, Cron significa a sintaxe padrão de agendamento baseada em tempo, que o n8n disponibiliza através da opção Custom (Cron) no Schedule Trigger.

O fuso horário do workflow é definido por workflow. Abra o workflow no canvas, selecione os três pontos no canto superior direito, selecione Settings e altere o valor de Timezone. Esta definição substitui GENERIC_TIMEZONE apenas nesse workflow.

Para um Schedule Trigger, a ordem é fixa. O n8n usa o fuso horário do workflow, se o workflow tiver um definido. Caso contrário, usa o fuso horário da instância indicado por GENERIC_TIMEZONE. Se nenhum dos dois estiver definido, usa o valor predefinido incorporado America/New_York. TZ não é consultado em nenhuma etapa dessa decisão.

Para datas dentro dos seus nós, a resposta depende do relógio que o código consulta. O Luxon, a biblioteca de datas usada pelas expressões do n8n, usa o fuso horário do n8n. Por isso, $now e $today seguem a mesma ordem workflow-then-instance do trigger. O JavaScript simples new Date() num nó Code consulta o sistema operativo e, por isso, segue TZ. Esta separação é a principal causa da confusão: o trigger pode estar correto, enquanto todos os timestamps gravados pelo workflow ficam desfasados várias horas.

Defina as três no arquivo Compose

Coloque TZ e GENERIC_TIMEZONE lado a lado no arquivo para evitar que alguém defina uma e se esqueça da outra. O trecho abaixo é a parte relevante do fuso horário de um serviço funcional. O restante do arquivo, o reverse proxy e o certificado vêm de um n8n auto-hospedado em uma VPS atrás de 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:

Aplique a alteração com docker compose up -d, não com docker compose restart. Um restart inicia novamente o mesmo container com o ambiente usado quando ele foi criado. Por isso, as alterações no arquivo não chegam ao processo em execução. up -d deteta o ambiente alterado e recria o container. Se mantiver estes valores em um arquivo env em vez de defini-los diretamente, a mesma regra de recriação se aplica. O guia arquivo env do Compose e gestão de secrets explica de onde esse arquivo é lido.

Use um nome de zona IANA (Internet Assigned Numbers Authority) no formato Region/City, como Europe/Berlin ou America/Sao_Paulo. Esses nomes contêm as regras de horário de verão do local. Por isso, o offset muda quando os relógios locais mudam. Um nome com offset fixo, como Etc/GMT+5, nunca muda com as estações do ano, e o sinal é invertido em relação ao que seria intuitivo. Execute LC_ALL=C TZ=Etc/GMT+5 date +%z para obter -0500. Evite esses nomes.

Por que definir apenas um deles deixa a correção incompleta

Defina apenas GENERIC_TIMEZONE e o Schedule Trigger será executado na hora pretendida, enquanto tudo o que lê o sistema operativo continuará em UTC. Um nó Code que chama new Date().toString() devolve uma string UTC, as linhas de log do contentor recebem timestamps em UTC e qualquer nome de ficheiro criado a partir do relógio do sistema muda para o dia seguinte à meia-noite errada.

Defina apenas TZ e acontece o contrário. docker compose exec n8n date apresenta a hora local, o que parece indicar que tudo está correto, enquanto o Schedule Trigger continua a usar America/New_York e é executado seis horas antes ou depois da hora solicitada. Esta é a variante que mais tempo consome, porque a verificação que a maioria das pessoas faz primeiro passa a indicar sucesso.

Defina um fuso horário para um workflow e altere GENERIC_TIMEZONE posteriormente: esse workflow ignora a alteração. O valor do workflow tem precedência e continua a ter precedência até alguém abrir as definições desse workflow. Quando um workflow é executado a uma hora estranha enquanto os workflows vizinhos funcionam corretamente, quase sempre esta é a causa.

Verifique os relógios em vez de adivinhar

Compare diretamente o host e o contentor.

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

Os dois primeiros comandos devem apresentar a mesma hora do sistema depois de TZ estar definido. printenv apresenta uma linha por cada variável existente. Portanto, duas linhas de saída significam que ambas estão definidas, e uma linha significa que está a observar o estado parcialmente corrigido.

Agora consulte o próprio n8n a partir de um workflow, porque uma shell do contentor não consegue indicar qual é o fuso horário ao nível do workflow. Adicione um nó Code ao workflow com comportamento incorreto e execute-o uma vez com Execute Workflow.

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

n8n_zone é o fuso horário que o Schedule Trigger deste workflow utilizará, já resolvido segundo a ordem workflow e depois instância. Portanto, responde diretamente à questão. system_time contém o fuso horário do próprio contentor, obtido de TZ. Execute-o no workflow com comportamento incorreto, e não num workflow novo, porque a definição ao nível do workflow é transportada com o workflow. Se esses dois valores forem diferentes, encontrou o problema sem abrir um único ficheiro de configuração.

Expressões Cron no nó Schedule Trigger

O Schedule Trigger oferece intervalos fixos de segundos a meses, além da opção Custom (Cron) para os casos que não são abrangidos por esses intervalos. A expressão cron é interpretada no fuso horário efetivo do workflow, portanto 0 6 * * * significa 06:00 nesse fuso, e não 06:00 UTC. Uma expressão de cinco campos do crontab guru pode ser colada diretamente. O n8n também aceita um campo opcional de segundos, que a tabela de campos da documentação coloca em primeiro lugar: segundo, minuto, hora, dia do mês, mês, dia da semana.

Nunca codifique o deslocamento manualmente. Escrever 0 4 * * * numa instância UTC para executar às 06:00 em Berlim está correto no inverno e fica uma hora adiantado durante todo o verão, porque Berlim usa UTC+1 no inverno e UTC+2 no verão. Defina o fuso horário e escreva a hora local pretendida.

O que o horário de verão faz a um job definido para as 02:30

Uma hora local no relógio não corresponde necessariamente a um instante específico. Duas vezes por ano, uma hora desaparece e outra repete-se, afetando qualquer job agendado dentro desses períodos. Pode observar este comportamento com date em qualquer sistema Linux, sem envolver o n8n.

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

O comando está correto. Em 2027-03-28, o relógio de Berlim avança diretamente das 02:00 para as 03:00. Por isso, as 02:30 locais não existem nesse dia, e date recusa convertê-las num instante. Um job associado às 02:30 locais não tem um momento em que possa ser executado. As horas adjacentes funcionam corretamente: date -d '2027-03-28 01:30' é resolvido como CET e date -d '2027-03-28 03:30' como CEST.

A transição de outono é o caso inverso. Em 2027-10-31, o relógio de Berlim recua das 03:00 para as 02:00, fazendo com que as 02:30 ocorram duas vezes.

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

São dois instantes diferentes, ambos identificados como 02:30 local, separados por 3600 segundos. Um job definido para esse horário pode ser executado duas vezes ou uma vez numa hora que ninguém escolheu. Nenhum desses resultados é adequado para uma execução de faturação ou uma rotação de backups. Retire o agendamento desse intervalo. Na maioria dos fusos horários europeus e norte-americanos, o período de risco vai das 00:00 às 03:00 locais.

Programe a infraestrutura em UTC e mostre a hora local às pessoas

A resposta padrão separa as duas funções de um fuso horário. As máquinas precisam de um intervalo estável. As pessoas precisam de uma hora legível.

  • Para tarefas que ninguém acompanha, defina o fuso horário do workflow como UTC. Backups, aquecimento de cache, envio de logs e geração de relatórios pertencem a esta categoria. Em UTC, o intervalo entre duas execuções é exatamente o intervalo que definiu, em todos os dias do ano, porque UTC não tem horário de verão.
  • Para tarefas que uma pessoa lê, mantenha a agenda em UTC e faça a conversão no momento da apresentação. Uma expressão faz isso: {{ $now.setZone('Europe/Berlin').toFormat('yyyy-MM-dd HH:mm') }} coloca a hora local no corpo da mensagem, enquanto o acionador permanece estável.

A mesma separação aplica-se fora do n8n. Quando parte da sua automação é executada como um serviço e um timer do systemd no VPS, a linha OnCalendar é lida no fuso horário do sistema, que é um quarto relógio com a sua própria configuração. Manter todos os agendadores em UTC deixa uma regra para memorizar, em vez de quatro. Isto também é importante para qualquer tarefa que resuma um período, porque um workflow de agente de IA do n8n solicitado a obter os números de ontem usará silenciosamente 24 horas diferentes, dependendo do fuso usado na resolução.

Modos de falha e a saída apresentada

Tudo é executado com cerca de seis horas de diferença. GENERIC_TIMEZONE nunca foi definido, por isso é aplicado o valor predefinido incorporado America/New_York. docker compose exec n8n printenv GENERIC_TIMEZONE não apresenta qualquer saída. Defina-o e recrie o contentor.

Editou o ficheiro Compose, mas nada mudou. Executou docker compose restart, por isso o contentor manteve o ambiente original. Execute docker compose up -d e confirme com docker compose exec n8n printenv TZ.

O acionador está correto, mas os timestamps estão errados. Apenas GENERIC_TIMEZONE está definido. Um new Date() num nó Code continua a ler UTC do sistema operativo. Defina TZ com o mesmo valor e recrie o contentor.

Um workflow ignora a definição da instância. Esse workflow tem o seu próprio fuso horário nas definições, que prevalece sobre GENERIC_TIMEZONE. Abra o canvas, selecione os três pontos, Settings e Timezone.

Um job diário foi executado duas vezes ou ignorou um dia, uma vez este ano. A hora agendada está dentro de uma transição para o horário de verão ou fora dele. Altere a hora ou mude esse workflow para UTC.

FAQ

Por que o meu gatilho de agendamento do n8n dispara à hora errada?

O workflow está a usar um fuso horário diferente daquele que assume. O n8n usa o fuso horário do workflow, se estiver definido; caso contrário, usa o fuso horário da instância em GENERIC_TIMEZONE; se este também não estiver definido, usa o valor predefinido integrado de America/New_York. Uma instância self-hosted em que ninguém definiu GENERIC_TIMEZONE agenda os trabalhos no horário de Nova Iorque, não em UTC. Por isso, a diferença raramente corresponde à sua própria diferença em relação a UTC. Execute docker compose exec n8n printenv GENERIC_TIMEZONE. Sem saída, significa que nunca foi definido.

Qual é a diferença entre TZ e GENERIC_TIMEZONE no n8n?

TZ é o fuso horário do sistema operativo dentro do contentor. Controla o resultado de date dentro do contentor, os timestamps apresentados nas linhas de log do contentor, o resultado de new Date() num nó Code e o que qualquer script executado no contentor deteta. GENERIC_TIMEZONE é o fuso horário da instância do n8n. É o valor usado pelos nós de agendamento e por expressões Luxon como $now. Definir uma variável sem definir a outra produz um gatilho correto com timestamps errados ou timestamps corretos com um gatilho que dispara várias horas fora de tempo. Defina ambas com o mesmo valor.

Devo definir o fuso horário do workflow ou GENERIC_TIMEZONE?

Defina GENERIC_TIMEZONE como predefinição para toda a instância e use a definição por workflow apenas quando um workflow pertencer realmente a outro fuso horário. O valor do workflow tem precedência sobre o valor da instância e não acompanha alterações posteriores a GENERIC_TIMEZONE. Por isso, uma substituição por workflow esquecida pode ser difícil de detetar meses mais tarde.

O que acontece a um job agendado para as 02:30 quando muda a hora?

Essa hora local pode desaparecer ou ocorrer duas vezes. LC_ALL=C TZ=Europe/Berlin date -d '2027-03-28 02:30' devolve date: invalid date '2027-03-28 02:30', porque nesse dia o relógio de Berlim avança das 02:00 para as 03:00. Em 2027-10-31, a mesma hora do relógio corresponde a dois instantes separados por uma hora. Evite agendar trabalhos entre as 00:00 e as 03:00 no horário local ou defina o workflow para UTC e converta para a hora local apenas no ponto em que uma pessoa consultar o valor.