跳到正文
DEV Community· HaoPeng Zhang·· 3 小時前AI 評分60

n8n 排程工作流因時區設定錯誤在凌晨 1 時執行

My 9 AM Workflow Ran at 1 AM: The Timezone Bug That Only Strikes in Production

AI 導讀

作者分享其 n8n 每日摘要工作流在生產環境因容器時區為 UTC,令設定為上午 9 時的排程在客戶當地凌晨 1 時執行,並在上游每日數據彙整完成前讀取舊數據。他建議部署時明確設定 GENERIC_TIMEZONE=Asia/Shanghai,並以首次執行記錄核對實際時間及時區。如時區採用夏令時間,文章建議避免安排在凌晨 1 時至 3 時,或使用 UTC 排程並在工作流內轉換。

正文

My 9 AM Workflow Ran at 1 AM

This is part of my build-in-public series on running LLM workflows in n8n in production. Everything here is from a real template I sell and support — the mistakes are mine, the fixes are tested.

The 1 AM email blast nobody asked for

Two months ago I shipped a daily digest workflow: every morning at 9 AM, it pulls yesterday's key metrics, asks an LLM to write a short summary, and emails it to a client. Simple. It worked perfectly for weeks.

Then the client pinged me: "Why did we get yesterday's digest at 1 AM?"

I hadn't changed anything. The Schedule Trigger was set to 9:00. The executions list said 9:00. And yet the email landed at 1:00 AM local time. My client was awake at 1 AM, reading a "good morning" summary. Not my finest moment.

The two clocks you're not looking at

There are (at least) two timezones in play with every scheduled n8n workflow, and most of us only think about one:

1. The server timezone. n8n evaluates Schedule Trigger cron expressions in the timezone of the machine (or container) it's running on. On your laptop, that's your local timezone — so 9:00 means 9:00 your time, and everything works. In production, your n8n is very likely running in a Docker container with TZ=UTC. Same cron expression, same trigger, now 9:00 means 9:00 UTC — which was 1:00 AM for my client (UTC+8).

2. The workflow's own assumptions. Inside the workflow, $now and the Date & Time node use the server clock too. My digest pulled "yesterday's metrics" using a date computed at runtime. When the workflow ran at 1 AM local instead of 9 AM, "yesterday" was still technically correct by the server clock — but the metrics job upstream hadn't finished its own daily rollup yet, so the summary was built on stale data. Wrong time and wrong data, from one timezone slip.

This is why the bug only strikes in production: dev (your laptop, local TZ) and prod (a container, UTC) quietly disagree, and nothing in n8n warns you.

The fix: pin the timezone, then verify it

The fix has two parts — configuration and a verification habit.

Part 1 — pin GENERIC_TIMEZONE. n8n has an environment variable, GENERIC_TIMEZONE, that controls the timezone for Schedule Triggers and the Date & Time node. If it's unset, n8n falls back to the server's timezone. I now set it explicitly in every deployment:

GENERIC_TIMEZONE=Asia/Shanghai

One line. The Schedule Trigger evaluates cron in that timezone regardless of what the container thinks the time is. (If you serve clients in multiple zones, pin to the zone of the audience for that workflow — a workflow can only have one evaluation timezone, so multi-audience schedules get separate triggers.)

Part 2 — verify with a canary execution. I don't trust config alone anymore. Every scheduled workflow I ship now starts with a two-node guard:

Schedule Trigger → Set (log execution_time: {{ $now.toFormat('yyyy-MM-dd HH:mm z') }})

The first real execution writes its actual local time into the execution log. If it doesn't say what I expect, I catch it on day one instead of hearing about it from a client at 1 AM. It takes thirty seconds to add and it has saved me twice since.

The DST bonus round

If your pinned timezone observes daylight saving time, cron expressions can double-fire or skip entirely on transition days (2:30 AM happens twice, or never). For anything that must run exactly once a day, I avoid the 1:00–3:00 AM window in DST-observing zones and prefer UTC-based scheduling with explicit conversion inside the workflow. Boring, but "runs twice on the spring-forward Sunday" is not a bug report you want.

The checklist I run now for every scheduled workflow

Before I ship any Schedule Trigger in n8n:

  1. GENERIC_TIMEZONE is set explicitly in the deployment (never inherited from the container)
  2. The cron time is expressed in the audience's timezone, not mine
  3. The workflow's first node logs the actual execution time with zone ($now with format + z), and I check the first real run
  4. Any "yesterday / today / this week" date math uses the pinned timezone, not the server default
  5. No daily trigger is scheduled between 1:00 and 3:00 AM in a DST-observing zone

Timezones are one of those things that are "obviously fine" until a client is reading your morning digest at 1 AM. Pin it, log it, check the first run.


I'm building AI Automation Lab — production-ready n8n workflow templates. The free AI Hook Generator shows how I structure real workflows; my open-source templates live at github.com/zhp910318/n8n-ai-automation-lab.

來源:DEV Community · dev.to