DEV Community

Pirate Prentice
Pirate Prentice

Posted on

n8n Schedule Trigger: CRON Expressions, Timezones, and Advanced Scheduling Patterns (Free Workflow JSON)

Most n8n users set up a Schedule Trigger by clicking "Every Day" and moving on. That works — until the workflow fires at 3 AM instead of 9 AM, or skips because of a timezone mismatch, or you need it to run every weekday but not weekends.

The Schedule Trigger node has more depth than the UI suggests. This guide covers CRON expression syntax, timezone handling, multi-schedule patterns, and how to combine the Schedule Trigger with the Wait node for precise time-based flows.

Section 1: The Schedule Trigger Node — What It Actually Does

The Schedule Trigger replaces the older Cron node (still usable, but deprecated). It fires your workflow on a time-based schedule with no external trigger required.

Modes

Every X [unit] — the simple mode. Pick minutes, hours, days, weeks, or months and a numeric interval. n8n constructs the underlying CRON expression for you.

Custom (CRON expression) — full control. Write a standard 5-field CRON expression directly.

Trigger at specific times — a UI-friendly mode for "9 AM every weekday" patterns. Under the hood, it still generates a CRON expression.

Section 2: CRON Expression Syntax — The Complete Reference

A CRON expression has 5 fields:

┌───────────── minute (059)
│ ┌───────────── hour (023)
│ │ ┌───────────── day of month (131)
│ │ │ ┌───────────── month (112 or JANDEC)
│ │ │ │ ┌───────────── day of week (07, both 0 and 7 = Sunday, or SUNSAT)
│ │ │ │ │
* * * * *
Enter fullscreen mode Exit fullscreen mode

Special characters

Character Meaning Example
* Every value * * * * * = every minute
, List of values 0 9,17 * * * = 9 AM and 5 PM daily
- Range 0 9-17 * * * = every hour from 9 AM to 5 PM
/ Step */15 * * * * = every 15 minutes
? No specific value (day fields only) 0 9 ? * MON-FRI = 9 AM weekdays
L Last 0 9 L * * = 9 AM on the last day of the month
# Nth weekday 0 9 * * 1#2 = 9 AM on the 2nd Monday of the month

Practical CRON patterns

# Every weekday at 9 AM
0 9 * * 1-5

# Every Monday at 8 AM
0 8 * * 1

# Every 15 minutes during business hours (9–5) on weekdays
*/15 9-17 * * 1-5

# First day of every month at 6 AM
0 6 1 * *

# Last day of every month at 11:55 PM
55 23 L * *

# Every quarter (1st of Jan, Apr, Jul, Oct) at midnight
0 0 1 1,4,7,10 *

# Twice daily: 8 AM and 6 PM
0 8,18 * * *

# Every 5 minutes, but only in the first 30 minutes of each hour
*/5 * * * * (use 0-30/5 for minute range if your CRON engine supports it)
Enter fullscreen mode Exit fullscreen mode

CRON testing before you deploy

Before saving, verify your expression at crontab.guru — paste the expression and confirm the "next runs" match your intent. A misplaced field (e.g., putting hour value in minute field) is the most common mistake.

Section 3: Timezone Handling — The #1 Source of Scheduling Bugs

The problem

n8n runs on the server's timezone by default. If your server is UTC and your users/customers are in Chicago (UTC-5/UTC-6), a workflow set to "9 AM" fires at 9 AM UTC — which is 3–4 AM Chicago time.

How n8n handles timezone

The Schedule Trigger node has a Timezone setting. Set it explicitly:

  1. Open the Schedule Trigger node
  2. Click Settings (gear icon in the node)
  3. Find Timezone
  4. Set to your target timezone (e.g., America/Chicago, Europe/London, Asia/Tokyo)

Always set timezone explicitly. Never rely on server default unless you control the server and never plan to migrate.

Finding the right timezone string

n8n uses IANA timezone names (the same format as JavaScript's Intl.DateTimeFormat):

America/New_York       (Eastern)
America/Chicago        (Central)
America/Denver         (Mountain)
America/Los_Angeles    (Pacific)
Europe/London          (GMT/BST)
Europe/Paris           (CET/CEST)
Asia/Tokyo             (JST)
Asia/Kolkata           (IST)
Australia/Sydney       (AEST/AEDT)
Enter fullscreen mode Exit fullscreen mode

Full list: en.wikipedia.org/wiki/List_of_tz_database_time_zones

Daylight saving time (DST) gotchas

IANA timezone names handle DST automatically. America/Chicago is UTC-6 in winter and UTC-5 in summer — n8n adjusts automatically.

Do NOT use fixed offsets (UTC+5:30, GMT-6) for recurring schedules — these don't adjust for DST and will silently drift by 1 hour twice a year.

Section 4: Common Scheduling Patterns with Workflow JSON

Pattern 1: Weekday morning digest (9 AM, Mon–Fri)

{
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 9 * * 1-5"
            }
          ]
        },
        "options": {
          "timezone": "America/Chicago"
        }
      },
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [250, 300]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Pattern 2: End-of-month report (last day, 5 PM)

For "last day of month" you need the L character in the day-of-month field:

{
  "parameters": {
    "rule": {
      "interval": [
        {
          "field": "cronExpression",
          "expression": "0 17 L * *"
        }
      ]
    },
    "options": {
      "timezone": "America/New_York"
    }
  },
  "name": "End of Month Trigger",
  "type": "n8n-nodes-base.scheduleTrigger",
  "typeVersion": 1.2,
  "position": [250, 300]
}
Enter fullscreen mode Exit fullscreen mode

Pattern 3: Rate-limited polling (every 5 minutes, with Wait node backoff)

When you poll an external API on a schedule and hit rate limits, combine Schedule Trigger + Wait node:

{
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [{ "field": "minutes", "minutesInterval": 5 }]
        }
      },
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [250, 300]
    },
    {
      "parameters": {
        "resume": "timeInterval",
        "value": 2,
        "unit": "seconds"
      },
      "name": "Rate Limit Wait",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [650, 300]
    }
  ],
  "connections": {
    "Schedule Trigger": {
      "main": [[{ "node": "Rate Limit Wait", "type": "main", "index": 0 }]]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Pattern 4: Multi-schedule with IF branching

One workflow, two schedules, different behavior:

{
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            { "field": "cronExpression", "expression": "0 6 * * 1-5" }
          ]
        }
      },
      "name": "Weekday 6 AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [250, 200]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            { "field": "cronExpression", "expression": "0 10 * * 6,0" }
          ]
        }
      },
      "name": "Weekend 10 AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [250, 400]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Wire both Schedule Triggers into a Merge node (Mode: Append), then use a Code node or IF node to branch on {{ $now.weekday() }} (0 = Monday in Luxon).

Section 5: Skipping Weekends and Holidays

Skipping weekends in CRON

Use 1-5 in the day-of-week field:

0 9 * * 1-5   # 9 AM Mon–Fri only
Enter fullscreen mode Exit fullscreen mode

Skipping holidays

CRON can't know about public holidays — that requires logic in your workflow:

// Code node: check if today is a holiday
const holidays = [
  '2026-01-01', // New Year's Day
  '2026-07-04', // Independence Day
  '2026-12-25', // Christmas
];

const today = $now.toFormat('yyyy-MM-dd');

if (holidays.includes(today)) {
  // Return no items — workflow continues but subsequent nodes get empty input
  return [];
}

return $input.all();
Enter fullscreen mode Exit fullscreen mode

Place this Code node immediately after the Schedule Trigger. If it returns [], downstream nodes process nothing and the workflow ends cleanly.

Section 6: Combining Schedule Trigger + Wait Node for Precise Timing

The Schedule Trigger fires once per interval. If you need a workflow to run at 9 AM, then pause, then run again at 2 PM within the same execution:

Schedule Trigger (9 AM) → [first batch of work] → Wait Node (5 hours) → [second batch of work]
Enter fullscreen mode Exit fullscreen mode

This is cleaner than two separate workflows when the second run depends on state from the first.

Wait Node config for this pattern:

  • Resume: Time Interval
  • Wait Amount: 5
  • Wait Unit: Hours

The execution holds its state across the wait. Variables set before the Wait node are still accessible after resume.

Section 7: Debugging Schedule Triggers

Test execution vs. live schedule

Clicking "Test step" in the editor fires the Schedule Trigger immediately (it doesn't wait for the next scheduled time). This is correct behavior — use it to test your downstream nodes without waiting.

To verify the schedule fires correctly at the right time: activate the workflow, then check Executions (sidebar) after the next scheduled time passes. Each execution shows its trigger time.

Common issues

Workflow not firing:

  • Is the workflow activated (toggle in top-right)? Inactive workflows don't run on schedule.
  • Did you save after changing the CRON expression? Changes don't take effect until saved + activated.
  • Check n8n process logs — if the n8n process was down during the scheduled time, the execution is skipped (no catch-up by default).

Wrong fire time:

  • Check timezone setting in the Schedule Trigger node settings.
  • Confirm your CRON expression with crontab.guru.
  • Remember: 0 9 * * * fires at 9:00:00 — seconds aren't a field in standard 5-field CRON.

Workflow fires but does nothing:

  • A Code node early in the workflow may be returning [] (e.g., a holiday check or conditional guard).
  • Check the execution details — the execution will show which node first produced empty output.

n8n CRON vs standard CRON: one difference

n8n's Schedule Trigger uses standard 5-field CRON (minute, hour, day-of-month, month, day-of-week). It does not support 6-field CRON with seconds. If you need sub-minute precision, use the "Every X Minutes" mode with minimum interval = 1 minute, then use a Wait node inside the workflow for finer delays.

Grab the Free Workflow JSON

The patterns above are bundled in the n8n Workflow Starter Pack — 10 production-ready workflows including the multi-schedule pattern, the holiday-skip Code node, and the combined Schedule + Wait flow:

👉 Get the n8n Workflow Starter Pack ($29)

Or grab just this article's workflows for free — drop a comment below and I'll paste the full JSON.


What's the most complex scheduling pattern you've built with n8n? Cron expression for the last business day of the month? Multi-timezone triggers? Share it in the comments — always curious what production n8n looks like.

Top comments (1)

Collapse
 
pirateprentice profile image
Pirate Prentice

What scheduling patterns are you running in n8n? Would love to hear if anyone's tackled the 'last business day of month' edge case — the cron expression for that one is surprisingly tricky.