n8nautomationscheduling

Your n8n Workflow Runs Twice and Renders Every Video Twice

··11 min read
Your n8n Workflow Runs Twice and Renders Every Video Twice

Clocks fell back at 02:00 local on Sunday, November 1, and US and Canadian n8n instances replayed the 01:00 hour.

When an n8n workflow runs twice, fix it in this order: move the Schedule Trigger out of the 01:00 to 03:00 local window, confirm you are on n8n's durable scheduler (the default since 2.45.0, and the one that is immune to the fall-back double fire), then key the render step on the source asset so a second pass returns the finished file instead of encoding it again.

Generic answers treat a duplicate execution as untidy. On a video pipeline it isn't: a nightly job that fires twice encodes every clip twice, uploads a second copy to the channel, and spends a second batch of tokens on footage you already rendered.

Two executions, one hour apart, in your n8n execution list

The symptom is two successful executions of the same workflow, from one Schedule Trigger, separated by exactly an hour. n8n issue #42070, filed on October 25, 2026, the day Europe fell back, records it on a Europe/Berlin instance where every daily trigger set to 03:xx fired twice:

Daily 03:00 schedule  →  2026-10-25 01:00:58 UTC   Success
Daily 03:00 schedule  →  2026-10-25 02:00:58 UTC   Success

Both runs show as normal successes, with no error, no warning, and nothing in the logs that says "daylight saving." The report covers both interval mode and cron mode, so switching between them changes nothing.

Downstream the giveaway is the artifacts, not the executions: two renders with different job IDs and byte-identical output, two Drive files with the same name and a (1) suffix, two uploads queued to the same channel minutes apart.

The three-step fix for a double-firing render pipeline

Three changes stop the duplicate render. The first removes the exposure, the second removes the bug, and the third makes the render safe to run twice no matter what fires it.

1. Move the trigger out of the repeated hour

Any daily schedule between 01:00 and 03:00 local in a DST zone sits in or next to the hour that repeats on fall-back night. In issue #42070, triggers at 02:45 and 04:xx were unaffected while 03:xx doubled, so the window is narrow but not where you'd guess. Pick 04:30 or 23:15 and the problem can't reach you twice a year.

Check which zone the node actually resolves first. n8n's Schedule Trigger docs say the node takes the workflow's timezone setting, falls back to the instance setting (America/New_York by default on self-hosted, detected or GMT on Cloud), and that IANA zone names handle DST automatically. That last assurance is the one that failed on October 25.

2. Confirm your instance is on the durable scheduler

The double fire is specific to n8n's legacy in-memory scheduler, which runs on cron 4.4.0. The durable scheduler, default for Schedule Trigger and Cron nodes since 2.45.0, uses cron-parser and is not affected, which is why PR #42078 only patches the old path. Checking which scheduler you're on is the fastest diagnostic:

# Anything printed here means you have opted out of the durable scheduler.
docker exec n8n printenv | grep -E 'N8N_SCHEDULER_ENABLED|N8N_SCHEDULER_TRIGGER_NODE_MODE'
docker exec n8n printenv | grep -E 'GENERIC_TIMEZONE|^TZ='
docker exec n8n n8n --version

Per n8n's durable scheduler docs, N8N_SCHEDULER_ENABLED now defaults to true and is deprecated, as is N8N_SCHEDULER_TRIGGER_NODE_MODE=legacy. If you set either one to keep clock-aligned "every N minutes" timing after upgrading, you're back on the code path that double-fires. Anything older than 2.45.0 is on the in-memory scheduler by default, and upgrading is the fix.

3. Key the render job on the source asset

Scheduling is only one way to get a second pass. A retried node, a re-queued execution, a worker that lost its lock, or a second webhook from the same event all produce a run n8n considers legitimate. The render step has to be safe to repeat, which means a key derived from the input, not from the run.

// Code node, between the trigger and the render step.
// $execution.id changes on every run, so it is useless as a dedupe key.
const crypto = require('crypto');

return items.map(item => {
  const source = item.json.sourceUrl;        // the asset about to be rendered
  const recipe = JSON.stringify({            // anything that changes the output
    outputFormat: 'mp4',
    preset: { quality: 'medium', resolution: '1080p' },
  });

  const renderKey = crypto
    .createHash('sha256')
    .update(`${source}|${recipe}`)
    .digest('hex')
    .slice(0, 32);

  return { json: { ...item.json, renderKey } };
});

Store renderKey alongside the job ID the render API returns, in Postgres, Airtable, or whatever store the workflow already touches. Look the key up before every submit. If a completed job exists, fetch its output and skip to the publish branch:

# Status checks and downloads are free on FFmpeg Micro, so the second pass costs nothing.
curl -s https://api.ffmpeg-micro.com/v1/transcodes/$JOB_ID \
  -H "Authorization: Bearer $FFMPEG_MICRO_API_KEY"
# {"success":true,"jobId":"...","status":"completed","outputUrl":"gs://...","lane":"short"}

curl -s https://api.ffmpeg-micro.com/v1/transcodes/$JOB_ID/download \
  -H "Authorization: Bearer $FFMPEG_MICRO_API_KEY"
# {"url":"https://storage.googleapis.com/...?X-Goog-Signature="}

Putting the recipe in the hash matters. Change the resolution later or add an overlay and the key changes, so the clip re-renders, which is what you want. Key on the source URL alone and you'll serve last week's output forever.

The same lookup belongs in front of the publish node, not just the render. Deduping the encode saves tokens; deduping the upload saves your channel from two identical posts, and that is the half your audience sees.

Why a daily Schedule Trigger double-fires when clocks go back

The cause is a date calculation that runs backwards inside the repeated hour. Issue #42070 traces it to getNextDateFrom(date, timeZone) returning a date earlier than the date passed in, on the in-memory scheduler running cron 4.4.0 with luxon 3.7.2. The scheduler takes that earlier time as the next run, fires, recalculates, and lands on the real time an hour later.

PR #42078 explains the mechanism in the library: CronTime.getNextDateFrom deliberately pulls the next run back an hour (30 minutes for Lord Howe Island) so a schedule inside the repeated hour isn't skipped, but it never checks that the pulled-back time still matches the cron expression. For a daily 03:00 schedule, 02:00 doesn't match, and it fires anyway.

That also answers why hourly schedules behaved. An hourly expression matches every hour, so firing at the pulled-back time is correct. Daily schedules break because for them the earlier time is one they were never supposed to run.

Doubles that look like daylight saving and are not

A duplicate n8n Schedule Trigger looks the same in the execution list as three other causes, all of which are getting blamed on the clocks this week. The gap between runs is your best clue: the DST bug produces exactly one hour and nothing else does.

  • Seconds apart, every interval, all year. Issue #27103 describes saving an active workflow re-registering its cron without deregistering the old one. Because toCronExpression randomizes the seconds field with randomInt(0, 60), the two registrations differ by seconds and both survive. The community's "delete the trigger node and re-add it" advice works here, and only here.
  • Two runs at the same instant in queue mode. Timer and poller work belongs to the leader main. A multi-main deployment without N8N_MULTI_MAIN_SETUP_ENABLED and shared Postgres and Redis has every main activating its own copy of every trigger, per n8n's queue mode docs. Queue mode has a related failure, covered in n8n execution crashed, startedAt null? It's a queue mode race.
  • One execution, two renders inside it. A render call that times out on the HTTP node after the job was already accepted, with Retry On Fail turned on, submits a second job. The execution list shows one run and your meter shows two.
  • A Canadian instance an hour off all week. Issue #39329 reports n8n bundling moment-timezone 0.5.48 (tzdb 2025b), which still had Alberta, the Northwest Territories, Manitoba, and British Columbia falling back on 2026-11-01. Recurrence checks read an hour off; the fix is moment-timezone 0.6.4 via PR #39330.

One limit on the three-step fix: none of it helps if the duplicate starts upstream, in a Webhook node receiving the same event twice from a platform that retries. The key still protects the render and the publish, but deduplication has to sit at the first node that sees the event. n8n's Remove Duplicates node can do that, with one catch: it only dedupes within a single execution unless you choose "Remove Items Processed in Previous Executions," and that mode has a History Size limit you can overflow.

What the second render costs on a per-second meter

A double fire costs n8n executions either way, but on the video step the arithmetic is sharper, because billing follows input duration. FFmpeg Micro meters one token per 10 seconds of input, per second, with no rounding up to the minute: a 10 second clip costs 10 seconds, and trimming a 30 second cut out of an hour-long recording costs 30 seconds, not the hour. Failed jobs are never billed, so the overnight crash isn't the expensive one.

Twelve one-minute clips a night is 72 tokens. Fired twice, 144. Across a week of silent duplicates that's 504 tokens against a Starter grant of 2,500, spent on files that already existed in your output bucket. The successful duplicate is the only double that costs anything, which is why the key goes on the asset and not the execution.

Submit-and-poll makes the dedupe cheap: you hold a job ID, and checking status or fetching a download URL costs nothing. More than 300 builders run FFmpeg Micro from Make or n8n, and the pattern that survives a bad night is the same one, store the job ID with the asset key and ask before you encode. The n8n integration page shows the node and the request shapes; the poll loop, including what to do with a failed status, is in Why your transcode job failed (and what to do in the poll loop).

FAQ about n8n workflows that run twice

Does upgrading to n8n 2.45.0 or later fix the double execution on its own?

Upgrading to 2.45.0 or later puts you on the durable scheduler, which uses cron-parser and doesn't double-fire on fall-back, so for the daylight saving cause the upgrade is enough. It does nothing about duplicate cron registrations, multi-main trigger activation, or retried render calls, so keep the asset key regardless.

Is a 02:30 trigger inside the repeated hour?

A 02:30 daily trigger in a North American zone sits outside the hour that repeated on November 1, 2026: US clocks went back at 02:00 local and replayed 01:00 to 02:00, per the Old Farmer's Almanac. Issue #42070 still shows a 03:00 trigger doubling while 02:45 did not, so move daily schedules outside 01:00 to 03:00 entirely rather than reasoning about the boundary twice a year.

Can I use an Idempotency-Key header on the render API instead?

Most render APIs, FFmpeg Micro included, don't accept an Idempotency-Key header, so deduplication has to live in your workflow's own state. Hash the source URL plus the output recipe, store that key with the returned job ID, and check the store before submitting. The status and download endpoints are free to call.

Which n8n daylight saving double execution am I looking at if the runs are seconds apart?

Runs separated by seconds rather than an hour are a duplicate cron registration, not a daylight saving problem. Saving an active workflow re-registers the trigger with a randomized seconds field and leaves the old registration live, so deleting and re-adding the Schedule Trigger node, then republishing, clears it.

Will a double-fired execution that dies halfway still cost me tokens?

A job that fails on FFmpeg Micro is never billed, so a duplicate execution that crashes before or during the encode costs nothing on the video meter. The expensive duplicate is the one that succeeds, and that's what the asset key catches.

Keying your render on the source asset is the change that survives the next time zone surprise, and it takes one Code node plus one lookup before the render. FFmpeg with n8n Cloud walks the submit-and-poll pipeline those nodes attach to. Nothing to install to try it: Sign up free and you get a free tier with no servers to run and no FFmpeg to maintain, callable from your code, from n8n, Make, and Zapier, or from your AI agents.

About Javid Jamae

Founder & CEO at FFmpeg Micro

Javid is a software engineer, author, and entrepreneur with over 25 years of professional software development experience across enterprise, startup, and consulting environments. He founded FFmpeg Micro to make video processing accessible to developers through a simple, automation-first REST API.

Software EngineeringVideo ProcessingFFmpegCloud ArchitectureAPI DesignAutomation

Ready to process videos at scale?

Start using FFmpeg Micro's simple API today. No infrastructure required.

Get Started Free