How to Fix n8n Schedule Trigger Firing at the Wrong Time in Docker: The Timezone Layer Cake

Introduction

You set a workflow to run at 09:00 Singapore time and it fired at 21:00. Or your daily report was supposed to go out at 8 AM and your team got it at 8 PM. If you self-host n8n in Docker, the Schedule Trigger reads the time from up to four different places, and they do not agree by default. This tutorial is for Docker self-hosters whose scheduled workflows fire hours off, and it walks you through diagnosing which layer is lying to you and fixing the stack in the one order that works.

Quick answer

Set the Timezone option on the Schedule Trigger node, then Workflow Settings, then GENERIC_TIMEZONE in docker-compose (never TZ alone, it only changes the container clock), restart the container, and verify with a five-minute canary schedule before trusting the fix.

Evidence: why this bug keeps happening

This is one of n8n’s most persistent support topics. As of October 22, 2026, the n8n repository carries 208,228 GitHub stars, and its community forum has hosted separate Schedule Trigger timezone threads since 2023, including threads numbered 23124, 45401, and 260595, where accepted answers contradict each other: some say set TZ in Docker, some say set GENERIC_TIMEZONE, some say use the workflow settings. The confusion is structural. The official n8n documentation defines the environment variable like this: “GENERIC_TIMEZONE … default America/New_York … The n8n instance timezone. Important for schedule nodes (such as Cron).” That single default is the trap: a fresh Docker install schedules everything in Eastern Time unless you say otherwise, no matter what TZ you put in your compose file. For background on the environment variable system, see the official n8n documentation on timezone and localization variables, and the scheduler environment variables reference for the durable scheduler that keeps firing through restarts. The n8n GitHub repository is the canonical source for Docker image behavior and issue history.

Prerequisites

  • A self-hosted n8n instance running in Docker or Docker Compose (any recent version; I verified the steps below against the current Docker image as of October 2026).
  • SSH or shell access to the host running the container, and permission to edit your docker-compose file.
  • One scheduled workflow you can use as a canary (create a test workflow if your production ones are too important to touch).
  • Basic comfort with YAML: indentation errors in docker-compose are the second most common way this fix fails.

Step 1: Diagnose which clock is lying

Before changing anything, find out exactly how far off you are and which layer is responsible. Open the workflow in the n8n editor, click the Schedule Trigger node, and look at the Settings tab inside the node (the gear icon). There is a Timezone field there. If it is empty, the node is not overriding anything. Next, click the three dots next to the workflow name at the top of the canvas and open Settings. The Timezone field shows the workflow-level value, often rendered as “Default — America/New York”. Finally, check the instance level from your shell:

docker exec <n8n-container-name> printenv GENERIC_TIMEZONE
docker exec <n8n-container-name> printenv TZ
docker exec <n8n-container-name> date

On a default install the output is revealing: GENERIC_TIMEZONE is empty (so n8n silently applies America/New_York), TZ is empty, and date prints UTC. Write down the actual firing time from the workflow’s Executions tab, convert it to your target timezone, and compare against the schedule. If the offset equals the gap between Eastern Time and your timezone, you have found the culprit: nothing in your stack ever overrode the America/New_York default.

Step 2: Understand the four-layer priority table

n8n resolves a Schedule Trigger’s firing time through these layers, highest priority first:

PriorityLayerWhere to set itExample
1Node Timezone optionSchedule Trigger node Settings tabAsia/Singapore
2Workflow Settings TimezoneWorkflow Settings (three-dot menu)Asia/Singapore
3GENERIC_TIMEZONEContainer environmentAsia/Singapore
4Nothing setn8n built-in defaultAmerica/New_York

Two gotchas are baked into this table. First, the container’s TZ variable does not appear in it at all: TZ changes what date prints inside the container and what shell scripts see, but the n8n scheduler never consults it. Second, layer 4 is not UTC, even though most Docker images run their OS clock on UTC. The container clock and the scheduler default are two different defaults, which is why docker exec ... date looking correct tells you nothing about when your workflow fires.

Step 3: Set the Timezone option on the Schedule Trigger node

For any workflow that must run in a specific local time, set it at layer 1 and stop depending on instance defaults. Open the Schedule Trigger node, go to its Settings tab, and set Timezone to the IANA name of your target zone, for example Asia/Singapore, America/Chicago, or Europe/Berlin. Always use IANA names, never fixed offsets like UTC+8 or GMT-6: IANA names shift automatically with daylight saving time, while fixed offsets drift by an hour every spring and autumn. Save the workflow. This one change is the most robust fix because it survives migrations: if you ever move the instance to a server in another country, this workflow still fires at the right local time.

Step 4: Set the workflow Settings Timezone as a project default

If you run dozens of workflows for one office, setting every node individually gets old. The workflow Settings Timezone acts as the default for every Schedule Trigger in that workflow that leaves its node option empty. Open the workflow settings and pick your timezone there. Community reports confirm the UI renders the untouched value as “Default — America/New York”, so if you see that line, the instance default is still in charge of this workflow. Set it explicitly to Asia/Singapore (or your zone) and save. Note that workflows inherit nothing from each other: a brand-new workflow starts at the instance default again, so make this part of your workflow-creation checklist.

Step 5: Fix the instance default with GENERIC_TIMEZONE

The instance default is the fix that protects every workflow at once, including ones other people created and forgot. Edit your docker-compose.yml:

services:
  n8n:
    image: docker.n8n.io/n8nio/n8n
    environment:
      - GENERIC_TIMEZONE=Asia/Singapore
      - TZ=Asia/Singapore
    # ... your volumes, ports, and database config

Setting both variables is deliberate. GENERIC_TIMEZONE fixes the scheduler, which is what this tutorial is about. TZ fixes the container clock, which matters for anything downstream that reads system time: Exec nodes, shell scripts, Code nodes calling new Date(), and the timestamps in your logs. The scheduler would still work with GENERIC_TIMEZONE alone, but leaving the two clocks in disagreement guarantees a confusing debugging session later. If you run queue mode, apply the same environment block to every service in the compose file (main instance and workers), because scheduling decisions and code execution can happen on different containers.

Then restart so the new variables take effect:

docker compose up -d
docker exec <n8n-container-name> printenv GENERIC_TIMEZONE

The second command should print Asia/Singapore. If it prints nothing, the compose edit did not apply: check YAML indentation and that you edited the file the daemon actually uses (run docker compose config and grep for GENERIC_TIMEZONE to be sure).

Step 6: Verify with a five-minute canary schedule

Never trust a timezone fix on a once-a-day schedule; you would wait 24 hours to learn you were still wrong. Build a canary: a new workflow with a Schedule Trigger set to run every minute (or every two minutes), followed by a single NoOp or Set node. Save it, activate it, wait six minutes, then open the Executions tab. Each execution row shows when it fired. Convert two consecutive firing times to your target timezone and confirm they land exactly on the minute marks you expect, with no drift and no skipped or doubled runs. If the executions tab shows times shifted by a whole offset, go back to Step 1: something in the priority table is still overriding you. Delete the canary when you are done, or keep it deactivated as a regression test for the next DST weekend.

Step 7: Audit for daylight saving time before the transition weekend

If your IANA zone observes DST, do one pass over your schedules twice a year. In spring, the clock jumps forward and the hour you skipped (for example 2:00 to 3:00 in US zones) simply never happens, so a schedule set for that hour fires zero times that day. In autumn, the clock falls back and the 1:00 hour happens twice, so a schedule at 1:30 AM can fire twice. Whether your n8n version deduplicates the doubled hour depends on the scheduler build, so for critical workflows (billing runs, compliance exports) move any 1:00 to 3:00 AM schedule off the transition Sunday, or set an explicit one-time override that week. Fixed-offset timezones are immune to this but create the year-round drift problem, so do not switch to them as a shortcut; the IANA name plus this audit is the correct combination.

Troubleshooting

The workflow fires at the right time when I click Execute, but wrong when scheduled. Manual execution ignores the schedule entirely; the trigger node just passes through with the current timestamp. Only published, active schedules go through the scheduler. Check that the workflow is actually activated (the toggle at the top of the canvas), then re-check the priority table: a canary on the live schedule, as in Step 6, is the only honest test.

I set TZ in docker-compose and restarted, but nothing changed. Expected. TZ is not in the scheduler’s priority table. Add GENERIC_TIMEZONE with the same value, restart again, and verify with printenv inside the container. This is the single most reported variant of the bug, and it comes directly from the two-defaults problem described in Step 2.

Firing times drift by minutes over weeks. True time drift is almost never the timezone code; it is a struggling container. Check host load, then stagger your schedules away from the top of the hour so competing workflows do not pile onto the same second. Our companion guide on fixing n8n 429 rate limit errors with staggered Schedule Triggers covers the same pileup from the API side and its minutes-drift patterns apply here too.

Executions show timestamps in a timezone I cannot read. The Executions tab renders in the browser’s timezone by default. For audit-grade comparisons, add a Set node that stores {{ $now }} and $now.toISO() so the execution data carries an unambiguous timestamp, or export execution logs to a system that records UTC.

After changing GENERIC_TIMEZONE, schedules are still off until I re-save each workflow. Environment changes apply at container start, but n8n caches each workflow’s computed schedule when it is activated. Deactivate and reactivate the affected workflows after the restart so the scheduler recomputes their next firing times under the new default. The durable scheduler keeps surviving restarts, but it does not retroactively reinterpret times computed under the old zone.

Conclusion

The n8n timezone bug is a priority-table bug: the Schedule Trigger node’s Timezone option beats the workflow Settings Timezone, which beats GENERIC_TIMEZONE, which beats the silent America/New_York default, while container TZ affects the scheduler not at all. Set all four layers deliberately, verify with a five-minute canary, and audit the DST transition weekends. If you are still building out your self-hosted n8n stack, our complete n8n self-hosting and automation guide walks through installation, scaling, and securing the instance, and the Docker installation tutorial covers the compose setup this fix builds on.

FAQ

Questions readers actually ask are answered in the FAQ section at the top of this article’s front matter and mirrored here.

Why does my n8n Schedule Trigger fire at the wrong time in Docker? Almost always because you set TZ in docker-compose but not GENERIC_TIMEZONE. TZ only changes the container OS clock, while n8n’s scheduler reads GENERIC_TIMEZONE, which defaults to America/New_York. A schedule that should fire at 09:00 in Singapore instead fires at 09:00 Eastern, which is 12 to 13 hours off.

Which timezone setting wins in n8n: node, workflow, or environment? The Schedule Trigger node’s Timezone option wins first, then the workflow Settings Timezone field, then the GENERIC_TIMEZONE environment variable. When none is set, n8n falls back to America/New_York, not the container’s UTC clock and not whatever TZ you set in docker-compose.

Do I need to restart n8n after changing GENERIC_TIMEZONE? Yes. Environment variables are read when the n8n process starts, so existing schedules keep running under the old timezone until the container restarts. Redeploy the container and confirm the new timezone by checking a fresh schedule’s next firing time.

Does daylight saving time break n8n schedules twice a year? It can shift or duplicate firings if you use fixed offsets. Use IANA names like America/New_York instead of UTC-5, because n8n adjusts automatically for DST. Watch the fall-back Sunday when clocks repeat an hour: a 1:30 AM schedule can fire twice, so audit critical 1 AM to 3 AM schedules before the transition weekend.

Why is my Code node’s timestamp one hour off from the Schedule Trigger? Inside a Code node, new Date() returns the container’s system time (UTC in most Docker images), while the Schedule Trigger honors your timezone settings. They are reading two different clocks. Use n8n’s $now helper, which is timezone-aware, whenever you compare trigger times against computed dates.

Is this fix needed for n8n Cloud, or only self-hosted? Mostly self-hosted Docker installs, where you control the environment variables. On n8n Cloud the platform sets GENERIC_TIMEZONE for you, but the node-level and workflow-level timezone options still apply, so the priority table in this guide is worth reading either way.

Frequently asked questions

Why does my n8n Schedule Trigger fire at the wrong time in Docker?

Almost always because you set TZ in docker-compose but not GENERIC_TIMEZONE. TZ only changes the container OS clock, while n8n's scheduler reads GENERIC_TIMEZONE, which defaults to America/New_York. A schedule that should fire at 09:00 in Singapore instead fires at 09:00 Eastern, which is 12 to 13 hours off.

Which timezone setting wins in n8n: node, workflow, or environment?

The Schedule Trigger node's Timezone option wins first, then the workflow Settings Timezone field, then the GENERIC_TIMEZONE environment variable. When none is set, n8n falls back to America/New_York, not the container's UTC clock and not whatever TZ you set in docker-compose.

Do I need to restart n8n after changing GENERIC_TIMEZONE?

Yes. Environment variables are read when the n8n process starts, so existing schedules keep running under the old timezone until the container restarts. Redeploy the container and confirm the new timezone by checking a fresh schedule's next firing time.

Does daylight saving time break n8n schedules twice a year?

It can shift or duplicate firings if you use fixed offsets. Use IANA names like America/New_York instead of UTC-5, because n8n adjusts automatically for DST. Watch the fall-back Sunday when clocks repeat an hour: a 1:30 AM schedule can fire twice, so audit critical 1 AM to 3 AM schedules before the transition weekend.

Why is my Code node's timestamp one hour off from the Schedule Trigger?

Inside a Code node, new Date() returns the container's system time (UTC in most Docker images), while the Schedule Trigger honors your timezone settings. They are reading two different clocks. Use n8n's $now helper, which is timezone-aware, whenever you compare trigger times against computed dates.

Is this fix needed for n8n Cloud, or only self-hosted?

Mostly self-hosted Docker installs, where you control the environment variables. On n8n Cloud the platform sets GENERIC_TIMEZONE for you, but the node-level and workflow-level timezone options still apply, so the priority table in this guide is worth reading either way.

TP
ToolPilot Team

We install, break, and fix automation tools so you don't have to. Every tutorial on ToolPilot is tested hands-on before publishing.

Keep reading