n8n 時區設定與 Schedule Trigger 排程時間錯誤
自架 n8n 會讀取 TZ、GENERIC_TIMEZONE 與工作流程時區,Schedule Trigger 可能差數小時。了解預設值為 America/New_York,並正確設定三層時區。
為什麼 n8n 排程觸發程序會在錯誤的時間執行
n8n 排程觸發程序會在錯誤的時間執行,是因為 n8n 會從三個不同位置讀取時區設定。修正其中一個位置,只能解決部分問題。這三個位置分別是容器本身的 TZ 變數、執行個體預設值 GENERIC_TIMEZONE,以及個別工作流程內設定的時區。一次設定好這三項設定,之後建立的每個排程都會在預期的時間執行。
先修正一個常見誤解。全新部署的自架 n8n 不會使用 UTC(協調世界時)排程。容器時鐘使用 UTC,因為官方映像未設定 TZ。排程是獨立的層級,而 n8n 文件所記載的 GENERIC_TIMEZONE 預設值為 America/New_York(截至 August 2026)。因此,未修改設定的執行個體會依 New York 時間執行 Schedule Triggers。這就是為什麼使用者回報的時差通常不符合其所在地與 UTC 的差距。位於 Berlin 的管理者若設定 06:00,會在當地時間 12:00 執行;而在 March 美國已切換至日光節約時間、Europe 尚未切換的數週內,則會在 11:00 執行。
三個時區層級,以及優先使用哪一個
TZ 是容器內的作業系統時區。n8n 文件將其描述為設定系統時區的變數,用來控制 date 等指令與指令碼的回傳值。它會決定容器內的 date 顯示內容、容器日誌每行所記錄的時間戳記、Code node 中 new Date() 的回傳值,以及你在容器內執行的任何 shell 指令碼所讀取的時區。它不會影響 Schedule Trigger 的觸發時間。
GENERIC_TIMEZONE 是 n8n 執行個體時區。文件稱其為 n8n 執行個體時區,並指出它對 Cron 等排程節點很重要。這裡的 Cron 是標準的時間排程語法,n8n 將其以 Schedule Trigger 中的 Custom (Cron) 選項提供。
工作流程時區是針對各個工作流程設定的。在畫布上開啟工作流程,選取右上角的三點圖示,選取 Settings,然後變更 Timezone 值。這會針對該工作流程覆寫 GENERIC_TIMEZONE。
Schedule Trigger 的判定順序是固定的。如果工作流程已設定時區,n8n 會使用工作流程時區;否則使用 GENERIC_TIMEZONE 中的執行個體時區;若兩者皆未設定,則使用內建預設值 America/New_York。在這個判定過程中,任何步驟都不會參考 TZ。
至於節點內的日期,則取決於程式碼要求哪一個時鐘。Luxon 是 n8n 運算式所使用的日期函式庫,會採用 n8n 時區,因此 $now 和 $today 遵循與觸發程序相同的「工作流程優先、其次為執行個體」順序。Code node 中的純 JavaScript new Date() 會查詢作業系統,因此遵循 TZ。這種分離是此問題大多數混淆的來源:觸發程序可能正確,但工作流程寫入的每個時間戳記卻可能相差數小時。
在 Compose 檔案中設定這 3 項
將 TZ 與 GENERIC_TIMEZONE 放在檔案中相鄰的位置,避免只設定其中一項而遺漏另一項。以下片段是可正常運作之服務中與時區相關的部分。檔案的其餘部分、反向代理與憑證,請參閱 VPS 上採用 HTTPS 的自架 n8n。
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:請使用 docker compose up -d 套用變更,不要使用 docker compose restart。重新啟動只會以建立容器時使用的環境再次啟動相同容器,因此檔案已變更,但執行中的程序不會更新。up -d 會偵測變更後的環境並重新建立容器。若您將這些值放在 env 檔案中,而不是直接寫入設定,仍須遵循相同的重新建立規則;Compose env 檔案與 secrets 處理指南說明該檔案的讀取位置。
在 Region/City 中使用 IANA(Internet Assigned Numbers Authority)時區名稱,例如 Europe/Berlin 或 America/Sao_Paulo。這些名稱包含該地區的日光節約時間規則,因此當當地時鐘調整時,UTC offset 也會變更。固定 offset 名稱(例如 Etc/GMT+5)不會隨季節變更,而且其正負號與一般直覺相反。執行 LC_ALL=C TZ=Etc/GMT+5 date +%z,輸出結果為 -0500。請避免使用這些名稱。
只設定其中一項為何只能解決一半問題
單獨設定 GENERIC_TIMEZONE 時,Schedule Trigger 會在指定的小時觸發,但所有讀取作業系統時間的元件仍使用 UTC。Code node 呼叫 new Date().toString() 時會回傳 UTC 字串,容器日誌行會標記為 UTC,而根據系統時鐘建立的檔名也會在錯誤的午夜切換日期。
單獨設定 TZ 時,情況正好相反。docker compose exec n8n date 會顯示您的本地時間,看起來像是設定成功,但 Schedule Trigger 仍使用 America/New_York,因此會比您指定的小時早或晚 6 小時觸發。這種情況最浪費時間,因為多數人首先執行的檢查現在會顯示正常。
設定工作流程時區後,若稍後變更 GENERIC_TIMEZONE,該工作流程會忽略這項變更。工作流程中的設定值優先,而且會持續生效,直到有人開啟該工作流程的設定為止。某個工作流程在異常時間執行,而其他工作流程都正常時,幾乎總是這個原因。
先檢查時鐘,不要靠猜測
直接比較主機與容器的時間。
date
docker compose exec n8n date
docker compose exec n8n printenv TZ GENERIC_TIMEZONE設定 TZ 後,前兩個指令應顯示相同的實際時間。printenv 會針對每個存在的變數輸出一行,因此輸出 2 行表示兩者都已設定,輸出 1 行表示目前仍處於只修正一半的狀態。
接著從工作流程內部詢問 n8n,因為容器 shell 無法告訴你工作流程層級的時區。將 Code 節點加入行為異常的工作流程,然後使用 Execute Workflow 執行一次。
return [
{
json: {
n8n_time: $now.toISO(),
n8n_zone: $now.zoneName,
system_time: new Date().toString(),
},
},
];n8n_zone 是此工作流程的 Schedule Trigger 將使用的時區。它已依照工作流程優先、執行個體其次的順序解析,因此可直接回答這個問題。system_time 會從 TZ 取得容器本身的時區。在行為異常的工作流程中執行,不要在新的工作流程中執行,因為工作流程層級的設定會隨工作流程保存。如果這兩個值不一致,就已經找出問題,完全不必開啟任何設定檔。
Schedule Trigger 節點中的 Cron 運算式
Schedule Trigger 提供從秒到月份的固定間隔,也提供 Custom (Cron),以涵蓋其他間隔無法表示的排程。Cron 運算式會依工作流程解析後的時區解讀,因此 0 6 * * * 代表該時區的 06:00,而不是 UTC 的 06:00。從 crontab guru 複製的五欄運算式可直接貼上使用。n8n 也接受選用的秒欄位;文件中的欄位表將其列在最前面:秒、分鐘、小時、每月的日、月份、星期幾。
不要手動將時差編碼。若要在 UTC 執行個體上設定柏林時間 06:00,寫入 0 4 * * * 只在冬季正確,整個夏季都會差 1 小時,因為柏林冬季為 UTC+1,夏季為 UTC+2。請設定時區,並直接填寫實際要使用的當地小時。
日光節約時間對設定為 02:30 的工作所造成的影響
本地牆上時鐘時間不代表固定的時間點。每年有兩次會少掉 1 小時,也會有 1 小時重複出現;排程在這些時段內的任何工作都會受到影響。你可以在任何 Linux 主機上使用 date 實際觀察這個情況,不需要使用 n8n。
LC_ALL=C TZ=Europe/Berlin date -d '2027-03-28 02:30'date: invalid date '2027-03-28 02:30'這不是命令中的錯字。2027-03-28,Berlin 的時鐘會直接從 02:00 跳到 03:00,因此當天不存在 02:30 的本地時間,date 無法將它轉換為固定時間點。以本地 02:30 為基準的工作沒有可執行的時間點。相鄰時間則沒有問題:date -d '2027-03-28 01:30' 會解析為 CET,而 date -d '2027-03-28 03:30' 會解析為 CEST。
秋季轉換則相反。2027-10-31,Berlin 的時鐘會從 03:00 撥回 02:00,因此 02:30 會出現 2 次。
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這是 2 個不同的時間點,兩者都稱為本地 02:30,相隔 3600 秒。固定在這個時間的工作可能執行 2 次,也可能在沒有人指定的時間點執行 1 次。這兩種結果都不符合帳務執行或備份輪替的需求。請將排程移出這個時段。在多數歐洲與北美時區,風險時段是本地時間 00:00 至 03:00。
以 UTC 排程基礎架構,向使用者顯示當地時間
標準做法是將時區的兩項工作分開。機器需要穩定的時間間隔;使用者需要易讀的時間。
- 對於無人查看的工作,將工作流程時區設為 UTC。備份、快取預熱、日誌傳送與報表產生都屬於此類。在 UTC 中,每次執行之間的間隔全年每天都完全符合設定值,因為 UTC 沒有日光節約時間。
- 對於使用者需要查看的工作,保留以 UTC 設定的排程,並在顯示時進行轉換。只需使用一個運算式:
{{ $now.setZone('Europe/Berlin').toFormat('yyyy-MM-dd HH:mm') }}會在訊息本文中填入當地時間,同時維持穩定的觸發時間。
相同的區分也適用於 n8n 以外的環境。當部分自動化工作以 VPS 上的 systemd service 與 timer 執行時,系統會依系統時區讀取其中的 OnCalendar 行;這是另一個具有獨立設定的時鐘。將所有排程器都設為 UTC,只需記住一項規則,而不是四項規則。這對彙整期間資料的工作也很重要,因為 n8n AI agent workflow 若要取得昨天的數據,會因解析時所使用的時區不同,悄悄採用不同的 24 小時區間。
失效情況與您會看到的輸出
所有項目都差了約 6 小時。 GENERIC_TIMEZONE 從未設定,因此套用內建預設值 America/New_York。docker compose exec n8n printenv GENERIC_TIMEZONE 完全沒有輸出。設定該值,然後重新建立容器。
您編輯了 Compose 檔案,但沒有任何變化。 您執行了 docker compose restart,因此容器仍使用原本的環境。執行 docker compose up -d,然後使用 docker compose exec n8n printenv TZ 確認。
觸發條件正確,但時間戳記錯誤。 只有 GENERIC_TIMEZONE 已設定。Code node 中的 new Date() 仍會從作業系統讀取 UTC。將 TZ 設為相同值,然後重新建立容器。
其中一個工作流程忽略了執行個體設定。 該工作流程在自己的設定中指定了時區,其優先順序高於 GENERIC_TIMEZONE。開啟畫布,選取三點選單、Settings、Timezone。
某個每日工作在今年某一天執行了兩次,或跳過了一天。 其排程時間落在日光節約時間轉換期間。調整執行時間,或將該工作流程改用 UTC。
FAQ
為什麼我的 n8n 排程觸發時間早了一個小時?
工作流程解析到的時區與您假設的時區不同。若工作流程已設定時區,n8n 會優先使用該時區;否則使用 GENERIC_TIMEZONE 中的 instance 時區,再否則使用內建預設值 America/New_York。如果 self-hosted instance 沒有人設定 GENERIC_TIMEZONE,排程會使用 New York 時間,而不是 UTC,因此與您所在時區和 UTC 的偏移通常不一致。執行 docker compose exec n8n printenv GENERIC_TIMEZONE。沒有輸出表示該設定從未設定過。
n8n 中的 TZ 與 GENERIC_TIMEZONE 有何不同?
TZ 是 container 內的作業系統時區。它會控制 container 內 date 的回傳值、container log 行顯示的時間戳記、Code node 中 new Date() 的回傳值,以及您在其中執行的任何 script 所讀取的時區。GENERIC_TIMEZONE 是 n8n instance 時區,也是 schedule node 與 $now 等 Luxon expression 使用的時區。只設定其中一個,可能造成觸發時間正確但時間戳記錯誤,或時間戳記正確但觸發時間相差數小時。請將兩者設為相同值。
應該設定 workflow 時區,還是 GENERIC_TIMEZONE?
將 GENERIC_TIMEZONE 設為整個 instance 的預設值。只有在某個 workflow 確實屬於其他時區時,才使用個別 workflow 設定。workflow 值會覆寫 instance 值,而且不會隨之後對 GENERIC_TIMEZONE 的變更更新,因此數月後很難追查被遺忘的個別 workflow 覆寫設定。
時鐘變更時,排程在 02:30 執行的工作會怎樣?
該當地時間可能消失,也可能出現兩次。因為當天 Berlin 時鐘會從 02:00 跳到 03:00,LC_ALL=C TZ=Europe/Berlin date -d '2027-03-28 02:30' 會回傳 date: invalid date '2027-03-28 02:30'。在 2027-10-31,同一個鐘面時間會對應到相隔一小時的兩個時間點。請避免將排程工作設在當地時間 00:00 到 03:00 的時段,或將 workflow 設為 UTC,僅在人員需要查看時轉換為當地時間。