n8nのSchedule Triggerがずれる原因と設定方法
n8nのSchedule Triggerが数時間ずれる原因は3つのタイムゾーン設定です。TZ、GENERIC_TIMEZONE、ワークフローのTime zoneを正しく設定する手順を解説します。
n8n のスケジュールトリガーが予定時刻と異なる時刻に実行される理由
n8n のスケジュールトリガーが予定時刻と異なる時刻に実行されるのは、n8n がタイムゾーンを3か所から読み込むためです。そのうち1か所だけを修正しても、問題の一部しか解決しません。対象は、コンテナ自体の TZ 変数、インスタンスのデフォルト値 GENERIC_TIMEZONE、個々のワークフロー内で設定されたタイムゾーンです。3か所すべてを一度設定すれば、その後に作成するスケジュールはすべて想定どおりの時刻に実行されます。
まず、よくある誤解を1つ正します。新規の self-hosted n8n は UTC(協定世界時)でスケジュールを実行するわけではありません。公式イメージでは TZ が設定されていないため、コンテナの時計は UTC です。スケジュールは別の層で処理されます。n8n の GENERIC_TIMEZONE に関するドキュメント上のデフォルト値は、2026年8月時点で America/New_York です。そのため、何も変更していないインスタンスの Schedule Trigger はニューヨーク時間で実行されます。報告される時差が、UTC から利用者の地域までの時差と一致しないことが多いのはこのためです。ベルリンの所有者が 06:00 を指定すると、現地時刻では 12:00 に実行されます。また、米国が夏時間へ移行した後、欧州がまだ移行していない3月の数週間は、11:00 に実行されます。
3 つのタイムゾーン層と、優先される設定
TZ は、コンテナ内のオペレーティングシステムのタイムゾーンです。n8n のドキュメントでは、システムのタイムゾーンを設定し、スクリプトや date などのコマンドが返す値を制御する変数と説明されています。コンテナ内で date が出力する内容、コンテナログの行に記録されるタイムスタンプ、Code node で new Date() が返す値、そこで実行するシェルスクリプトが参照する時刻は、この設定で決まります。ただし、Schedule Trigger が実行される時刻には影響しません。
GENERIC_TIMEZONE は、n8n インスタンスのタイムゾーンです。ドキュメントでは n8n インスタンスのタイムゾーンと呼ばれ、Cron などのスケジュールノードで重要だと説明されています。ここでいう Cron は、標準的な時刻ベースのスケジュール構文を指します。n8n では、Schedule Trigger の Custom (Cron) オプションとして利用できます。
ワークフローのタイムゾーンは、ワークフローごとに設定します。キャンバスでワークフローを開き、右上の 3 つのドットを選択してから Settings を選択し、Timezone の値を変更します。この設定は、そのワークフローに限り GENERIC_TIMEZONE を上書きします。
Schedule Trigger では、適用順序が固定されています。ワークフローにタイムゾーンが設定されている場合はそれを使用します。設定されていない場合は、GENERIC_TIMEZONE のインスタンスタイムゾーンを使用します。それも設定されていない場合は、組み込みのデフォルトである America/New_York を使用します。この判定で TZ はどの段階でも参照されません。
ノード内で日付を扱う場合は、コードがどの時計を参照するかによって結果が異なります。n8n の式処理で使用される日付ライブラリの Luxon は n8n のタイムゾーンを使用するため、$now と $today はトリガーと同じく、ワークフロー、次にインスタンスという順序に従います。Code node 内の通常の JavaScript new Date() はオペレーティングシステムに問い合わせるため、TZ に従います。この違いが、ここで混乱が生じる主な原因です。トリガーは正しく動作していても、ワークフローが記録するすべてのタイムスタンプが数時間ずれることがあります。
Compose ファイルですべてを設定する
ファイル内で TZ と GENERIC_TIMEZONE を隣り合わせに配置し、一方だけ設定してもう一方を忘れないようにします。以下の断片は、正常に動作するサービスのタイムゾーン関連部分です。ファイルの残りの部分、リバースプロキシと証明書の設定については、HTTPS を使用して VPS の背後で自己ホストする 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 restart ではなく docker compose up -d を使用して適用します。再起動では、作成時の環境変数を使用して同じコンテナが再び起動するだけです。そのため、ファイルは変更されても、実行中のプロセスには反映されません。up -d は変更された環境変数を検出し、コンテナを再作成します。これらの値をインラインではなく env ファイルに記述する場合も、同じ再作成ルールが適用されます。Compose の env ファイルと Secret の扱いでは、そのファイルがどこから読み込まれるかを説明しています。
Region/City には、Europe/Berlin や America/Sao_Paulo のような IANA (Internet Assigned Numbers Authority) のゾーン名を使用します。これらの名前には、その地域の夏時間の規則が含まれているため、現地の時計が変わると UTC オフセットも変わります。Etc/GMT+5 のような固定オフセット名は季節によって変化せず、符号も推測とは逆になります。LC_ALL=C TZ=Etc/GMT+5 date +%z を実行すると、-0500 と表示されます。これらの名前は使用しないでください。
片方だけ設定すると不完全な修正になる理由
GENERIC_TIMEZONEだけを設定すると、Schedule Triggerは指定した時刻に実行されます。一方、オペレーティングシステムを参照する処理はUTCのままです。new Date().toString()を呼び出すCode nodeはUTCの文字列を返し、コンテナのログ行にはUTCの時刻が記録されます。システムクロックを基に生成したファイル名も、誤った深夜0時に日付が切り替わります。
TZだけを設定すると、逆の状態になります。docker compose exec n8n dateはローカル時刻を出力するため、設定が成功したように見えます。しかし、Schedule TriggerはAmerica/New_Yorkのままで、指定した時刻から6時間ずれて実行されます。これは、最初に行う確認が通るようになるため、最も時間を浪費しやすいパターンです。
ワークフローのタイムゾーンを設定した後でGENERIC_TIMEZONEを変更しても、そのワークフローには変更が反映されません。ワークフロー側の設定が優先され、誰かがそのワークフローの設定を開いて変更するまで有効です。周囲のワークフローは正常なのに、1つだけ異常な時刻に実行される場合は、ほぼ確実にこの状態です。
推測せずに時刻を確認する
ホストとコンテナを直接比較します。
date
docker compose exec n8n date
docker compose exec n8n printenv TZ GENERIC_TIMEZONETZを設定すると、最初の2つのコマンドは同じ現在時刻を表示するはずです。printenvは存在する変数ごとに1行を表示します。そのため、出力が2行なら両方が設定されています。1行なら、片方だけ修正された状態です。
次に、ワークフロー内部から n8n 自体に確認します。コンテナのシェルでは、ワークフロー単位のタイムゾーンは分からないためです。問題が発生するワークフローに Code ノードを追加し、Execute Workflow で1回実行します。
return [
{
json: {
n8n_time: $now.toISO(),
n8n_zone: $now.zoneName,
system_time: new Date().toString(),
},
},
];n8n_zoneは、このワークフローの Schedule Trigger が使用するゾーンです。ワークフロー、次にインスタンスという順序で解決済みのため、質問に直接答えられます。system_timeには、TZから取得したコンテナ自身のゾーンが含まれます。ワークフロー単位の設定はワークフローに紐付いているため、新しいワークフローではなく、問題が発生するワークフローで実行してください。この2つの値が一致しなければ、設定ファイルを1つも開かずに問題を特定できます。
Schedule Trigger ノードの Cron 式
Schedule Trigger では、秒から月までの固定間隔を設定できます。これらで対応できない場合は、Custom (Cron) を使用します。Cron 式はワークフローで解決されたタイムゾーンで解釈されます。そのため、0 6 * * * は UTC の 06:00 ではなく、そのタイムゾーンの 06:00 を意味します。crontab guru の5フィールド式は、そのまま貼り付けて使用できます。n8n では、秒フィールドを任意で追加することもできます。ドキュメントのフィールド表では、秒、分、時、日、月、曜日の順に先頭へ配置します。
オフセットを手動で指定してはいけません。UTC インスタンスでベルリンの 06:00 を実行するために 0 4 * * * と記述すると、冬は正しく動作しますが、夏は1時間ずれます。ベルリンは冬に UTC+1、夏に UTC+2 となるためです。タイムゾーンを設定し、実行したい現地時刻の時を記述してください。
02:30 に設定したジョブに夏時間が与える影響
現地の壁時計の時刻は、必ずしも特定の瞬間を示すとは限りません。年に 2 回、1 時間が消える日と 1 時間が繰り返される日があり、その時間帯に設定されたジョブは影響を受けます。n8n を使わなくても、Linux マシン上で date を使用して動作を確認できます。
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どちらも現地時刻では 02:30 と呼ばれますが、異なる 2 つの瞬間であり、3600 秒離れています。その時刻に固定したジョブは 2 回実行されるか、誰も指定していない 1 時間ずれた時刻に 1 回実行されます。請求処理やバックアップのローテーションでは、どちらの結果も望ましくありません。スケジュールをこの時間帯から移動してください。ヨーロッパと北米の多くのタイムゾーンでは、00:00 から 03:00 の現地時間が危険な範囲です。
インフラのスケジュールは UTC で設定し、利用者には現地時刻を表示する
タイムゾーンが担う役割は、2 つに分けて考えるのが基本です。マシンには安定した時間間隔が必要です。利用者には読みやすい時刻が必要です。
- 人が監視しない処理では、ワークフローのタイムゾーンを UTC に設定します。バックアップ、キャッシュのウォームアップ、ログの転送、レポートの生成が該当します。UTC には夏時間がないため、1 年のどの日でも、2 回の実行間隔は指定した間隔と正確に一致します。
- 人が読む処理では、スケジュールを UTC のままにし、表示時点で変換します。1 つの式で実現できます。
{{ $now.setZone('Europe/Berlin').toFormat('yyyy-MM-dd HH:mm') }}により、トリガーを安定させたまま、メッセージ本文に現地時刻を入れられます。
この分離は n8n 以外にも適用できます。自動化の一部を VPS 上の systemd サービスとタイマーとして実行する場合、その OnCalendar 行はシステムのタイムゾーンで読み込まれます。これは独自の設定を持つ 4 つ目の時計です。すべてのスケジューラーを UTC に統一すれば、覚えるルールを 4 つではなく 1 つにできます。期間を集計する処理では特に重要です。n8n AI agent のワークフローが昨日の数値を要求した場合、どのタイムゾーンで解釈されたかによって、対象となる 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 に同じ値を設定してから、コンテナを再作成してください。
1 つのワークフローだけインスタンス設定を無視します。 そのワークフロー独自のタイムゾーンが設定されており、GENERIC_TIMEZONE より優先されています。キャンバスで、3 点メニュー、Settings、Timezone の順に開いてください。
今年、1 日単位のジョブが 2 回実行されたか、1 日分スキップされました。 スケジュール時刻が daylight saving transition の範囲内にあります。実行時刻を変更するか、そのワークフローを UTC に変更してください。
FAQ
n8n のスケジュールトリガーが誤った時刻に実行されるのはなぜですか?
ワークフローが、想定とは異なるタイムゾーンを解決しています。n8n は、ワークフローにタイムゾーンが設定されていればそれを使用し、設定されていなければ GENERIC_TIMEZONE のインスタンスタイムゾーンを使用します。どちらも設定されていない場合は、組み込みのデフォルト値 America/New_York を使用します。GENERIC_TIMEZONE を設定していない self-hosted インスタンスでは、UTC ではなく New York 時間でスケジュールされます。そのため、UTC との差が自分の環境での差と合わないことがよくあります。docker compose exec n8n printenv GENERIC_TIMEZONE を実行してください。出力がなければ、設定されたことがありません。
n8n における TZ と GENERIC_TIMEZONE の違いは何ですか?
TZ は、コンテナ内のオペレーティングシステムのタイムゾーンです。コンテナ内で date が返す値、コンテナのログ行に表示されるタイムスタンプ、Code node で new Date() が返す値、およびコンテナ内で実行するスクリプトが認識するタイムゾーンを制御します。GENERIC_TIMEZONE は n8n インスタンスのタイムゾーンです。スケジュールノードと $now などの Luxon 式はこれを使用します。一方だけを設定すると、トリガーは正しいがタイムスタンプが誤るか、タイムスタンプは正しいがトリガーが数時間ずれて実行されます。両方に同じ値を設定してください。
ワークフローのタイムゾーンと GENERIC_TIMEZONE のどちらを設定すべきですか?
インスタンス全体のデフォルトとして GENERIC_TIMEZONE を設定し、特定のワークフローが別のタイムゾーンに属する場合だけ、ワークフロー単位の設定を使用してください。ワークフローの値はインスタンスの値より優先されます。また、後から行った GENERIC_TIMEZONE の変更には追従しません。そのため、ワークフロー単位の設定を解除し忘れると、数か月後に原因を特定しにくくなります。
時計が変わるとき、02:30 にスケジュールされたジョブはどうなりますか?
その現地時刻が存在しなくなるか、2 回発生します。LC_ALL=C TZ=Europe/Berlin date -d '2027-03-28 02:30' は date: invalid date '2027-03-28 02:30' を返します。その日は Berlin の時計が 02:00 から 03:00 に進むためです。2027-10-31 には、同じ時計上の時刻が 1 時間離れた 2 つの時点に対応します。スケジュールされた処理を現地時間の 00:00 から 03:00 の範囲に置かないでください。または、ワークフローを UTC に設定し、人が読む箇所でだけ現地時間に変換してください。