AWS EventBridge Cron Expressions: 24 Examples, Time Zones, Rate vs Cron, Rules vs Scheduler, and "ScheduleExpression Is Not Valid" Fixed

Logeshwaran
—

An AWS EventBridge cron expression has six fields, not five: cron(minutes hours day-of-month month day-of-week year), for example cron(0 9 ? * MON-FRI *) for 9:00 every weekday. Three rules trip almost everyone: one of the two day fields must be ? (you cannot use * in both), day-of-week starts at 1 = Sunday (so 2-6 is Monday to Friday, unlike Linux cron), and the shortest interval is 1 minute (no seconds field). For "every N" jobs a rate expression is simpler: rate(5 minutes), rate(1 hour). Time zone: EventBridge Scheduler runs cron and one-time schedules in any IANA zone you set (Europe/London, Australia/Sydney, America/New_York) and adjusts for daylight saving; the older scheduled rules are UTC only. Below: every field and wildcard, 24 ready-made expressions, time zones and daylight saving, rate vs cron, rules vs Scheduler, Lambda set-up in the CLI, Python, Terraform and SAM, the "ScheduleExpression is not valid" causes, and how other cron dialects convert.

The shop wanted its abandoned-cart emails to go out at nine every weekday morning, London time. Ethan typed cron(0 9 * * MON-FRI *), the console refused it, and he spent twenty minutes searching for a typo that was not there. When he finally got it saved, the emails arrived at ten in the morning all summer, because the schedule was running at nine UTC. Two small rules, one wasted afternoon; this page is so you skip both.

⚡ Quick Answer

• Six fields: minutes, hours, day-of-month, month, day-of-week, year. Fields.

• Copy one: 24 common schedules, ready to paste. Examples.

• Local time: EventBridge Scheduler plus --schedule-expression-timezone. Time zones.

• Rejected? Usually * in both day fields, five fields, or a seconds field. Errors.

Rate vs cron · Rules vs Scheduler · Schedule a Lambda · Linux, GitHub, Spring, Azure · Pricing.

New to EventBridge or Lambda? The cards below cover EventBridge, Lambda, and the two troubleshooting guides for schedules that do not fire.

🧭 NEW HERE? READ THESE FIRST

New to EventBridge or Lambda? These five pages give you the background this one builds on:

📌 Bookmark this; the examples table is the part you will come back for.

The six fields of an EventBridge cron expression

What is a cron expression? It is a compact way to write "when" as a short line of text: which minute, which hour, which days. The idea comes from the Unix cron scheduler, but AWS uses its own six-field version, closer to the Quartz scheduler from the Java world than to Linux. The same syntax works in EventBridge Scheduler, legacy EventBridge scheduled rules, and anything built on them (ECS scheduled tasks, Systems Manager maintenance windows, and many others).

PositionFieldAllowed valuesWildcards
1Minutes0-59, - * /
2Hours0-23, - * /
3Day-of-month1-31, - * ? / L W
4Month1-12 or JAN-DEC, - * /
5Day-of-week1-7 or SUN-SAT (1 = Sunday), - * ? L #
6Year1970-2199, - * /

What each wildcard does, with the example you will actually type:

  • * means every value: * in hours is every hour.
  • ? means "any", and exists because day-of-month and day-of-week overlap. If you set one day field (or put * in it), the other must be ?. This is the rule behind most rejected expressions.
  • , lists values: MON,WED,FRI, or 8,20 in hours for 8:00 and 20:00.
  • - is a range: MON-FRI, or 8-17 in hours.
  • / is a step: 0/15 in minutes is 0, 15, 30 and 45; 1/10 is 1, 11, 21 and so on.
  • L is "last": L in day-of-month is the last day of the month; 6L in day-of-week is the last Friday.
  • W is the nearest weekday: 15W is the weekday closest to the 15th.
  • # is "the Nth weekday of the month": 2#1 is the first Monday, 3#2 the second Tuesday. Only one # term is allowed in the field.

To read any expression someone hands you, go field by field:

  1. Take cron(0/10 8-17 ? * MON-FRI *) and split it on spaces into six parts.
  2. Minutes 0/10: at 0, 10, 20, 30, 40 and 50 past the hour.
  3. Hours 8-17: from 8 a.m. through the 5 p.m. hour (so the last run is 17:50).
  4. Day-of-month ?: no restriction from this field.
  5. Month *: every month.
  6. Day-of-week MON-FRI: weekdays only. Year *: every year. Put together: "every ten minutes during working hours on weekdays".

Jake: "Why did the console reject Ethan's version? It looked fine."

Ethan: "I had a star in day-of-month and MON-FRI in day-of-week. One of them has to be a question mark."

24 EventBridge cron expressions you can copy

Think of this as a cron expression generator you do not have to type into. Each line is valid in both EventBridge Scheduler and scheduled rules. Times are in whatever time zone your schedule uses (UTC for rules and for schedules without a zone).

You wantExpression
Every minuterate(1 minute) or cron(* * * * ? *)
Every 5 minutesrate(5 minutes) or cron(0/5 * * * ? *)
Every 15 minutescron(0/15 * * * ? *)
Every 30 minutescron(0/30 * * * ? *)
Every hour, on the hourcron(0 * * * ? *)
Every 2 hourscron(0 0/2 * * ? *)
Every 6 hourscron(0 0/6 * * ? *) or rate(6 hours)
Every day at midnightcron(0 0 * * ? *)
Every day at 9:00cron(0 9 * * ? *)
Every day at 8:00 and 20:00cron(0 8,20 * * ? *)
Every day at 14:05 and 14:35cron(5,35 14 * * ? *)
Weekdays at 9:00cron(0 9 ? * MON-FRI *)
Weekdays, every 10 minutes, 8:00-17:50cron(0/10 8-17 ? * MON-FRI *)
Weekends at 10:00cron(0 10 ? * SAT,SUN *)
Every Monday at 8:00cron(0 8 ? * MON *)
Monday, Wednesday, Friday at 7:30cron(30 7 ? * MON,WED,FRI *)
First day of every month at 00:00cron(0 0 1 * ? *)
Last day of every month at 23:00cron(0 23 L * ? *)
Last Friday of the month at 18:00cron(0 18 ? * 6L *)
First Monday of the month at 8:00cron(0 8 ? * 2#1 *)
Weekday nearest the 15th at 9:00cron(0 9 15W * ? *)
Quarterly (Jan, Apr, Jul, Oct 1st)cron(0 0 1 1,4,7,10 ? *)
Once a year, January 1st at 00:00cron(0 0 1 1 ? *)
Once only (Scheduler)at(2026-12-31T23:59:00) plus a time zone

Before you save any of them, the console shows a preview of the next ten run times. That preview is the best cron expression validator you have: if the dates look wrong, the expression is wrong, whatever it looked like when you typed it.

Build, check and validate an AWS cron expression

Online "cron expression builders" and "generators" are popular, but many produce Linux cron (five fields) or Quartz cron (seven fields with seconds), which AWS rejects. Building one by hand is quicker once you follow a fixed order:

  1. Write the time first: minutes, then hours, in UTC for rules (or in your chosen time zone for Scheduler). 9:30 am is 30 9.
  2. Decide day-of-month or day-of-week: you can use only one; the other field gets ?. Every weekday is ? * MON-FRI; the 1st of every month is 1 * ?.
  3. Fill the month: * for every month, or a list such as JAN,APR,JUL,OCT.
  4. Add the year: almost always *.
  5. Wrap it: cron(30 9 ? * MON-FRI *).
  6. Check the next run times before you rely on it (below).

Validating is easiest in the console: when you type a cron pattern while creating a schedule or rule, the EventBridge console lists the next trigger dates, so a mistake such as a schedule that runs on the wrong day shows up immediately. From the command line, create the schedule in the disabled state; an invalid expression fails at once with a ValidationException, and a valid one can be checked and then enabled:

aws scheduler create-schedule --name test-cron --state DISABLED \
  --schedule-expression "cron(30 9 ? * MON-FRI *)" --schedule-expression-timezone "Europe/London" \
  --flexible-time-window Mode=OFF \
  --target Arn=arn:aws:lambda:eu-west-2:111122223333:function:report,RoleArn=arn:aws:iam::111122223333:role/scheduler-invoke

The mistakes a validator catches most often: five fields instead of six, * in both day fields instead of one ?, a day-of-week number from Linux cron (where Sunday is 0, but AWS uses 1 to 7 with Sunday as 1), and a seconds field copied from a Quartz example.

Jake: "The online generator gave me 0 0 9 ? * MON-FRI and AWS rejected it."

Ethan: "That's Quartz: it starts with seconds. Drop the first zero and add a year field at the end."

Time zones and daylight saving

This is the half of Ethan's problem that the console did not catch. A schedule with no time zone runs in UTC. London is UTC in winter and UTC+1 in summer, so cron(0 9 ? * MON-FRI *) in UTC fires at 10:00 London time from late March to late October.

EventBridge Scheduler fixes this properly. You give it an IANA time zone name, and it evaluates cron and one-time (at) schedules in that zone, adjusting for daylight saving on its own. To set it up:

  1. Pick the IANA name for your city: Europe/London, Australia/Sydney, Australia/Melbourne, America/New_York, America/Los_Angeles, Asia/Kolkata. Abbreviations such as "EST" or "BST" are not the right input; the city name is.
  2. Write the cron expression in local time: cron(0 9 ? * MON-FRI *) means 9:00 in that zone.
  3. Pass the zone: --schedule-expression-timezone "Europe/London" in the CLI, ScheduleExpressionTimezone in the API, CloudFormation and SAM, schedule_expression_timezone in Terraform.
  4. Check the next-ten-runs preview in the console, or read the schedule back with aws scheduler get-schedule --name cart-emails-london.

Daylight saving has two edge cases you should know about when a job runs in the small hours:

  • Spring forward: if your time does not exist that night (for example 2:30 a.m. when clocks jump from 1:59 to 3:00), that day's run is skipped, and the schedule carries on normally the next day.
  • Fall back: if your time happens twice (clocks go from 2:59 back to 2:00), the job runs once, not twice.
  • Rate schedules in days count 24 real hours from the last run, so rate(1 day) drifts by an hour of local clock time across a daylight saving change. Use cron if the wall-clock time matters.
  • Zones that do not observe daylight saving (India, Queensland in Australia, UTC itself) never shift.

Legacy scheduled rules are UTC only. If you are stuck with a rule, convert by hand, and remember the day can change too. A few worked conversions for 9:00 local on weekdays:

9:00 weekdays inRule in UTC (standard time)Rule in UTC (daylight saving)
Londoncron(0 9 ? * MON-FRI *)cron(0 8 ? * MON-FRI *)
New Yorkcron(0 14 ? * MON-FRI *)cron(0 13 ? * MON-FRI *)
Sydneycron(0 23 ? * SUN-THU *) (the day before, in UTC)cron(0 22 ? * SUN-THU *)
Indiacron(30 3 ? * MON-FRI *) all year (no daylight saving)

The Sydney row is the one that bites: Monday 9:00 in Sydney is still Sunday in UTC, so the day-of-week range shifts back by one. With Scheduler and Australia/Sydney you simply write MON-FRI and stop thinking about it, which is the best argument for moving off rules.

Jake: "So we'd have needed two rules, one for summer and one for winter?"

Ethan: "Or one schedule with Europe/London on it. I deleted the rule."

Rate or cron: which one to use

A rate expression is "every N units": rate(value unit) with minutes, hours or days. Use a value of 1 with the singular unit (rate(1 minute), rate(1 hour), rate(1 day)) and anything larger with the plural (rate(5 minutes)); rules reject rate(1 hours) and rate(5 hour), so that habit keeps you safe everywhere.

  • Use rate when only the interval matters: poll a queue every 5 minutes, refresh a cache every hour. A rate schedule starts counting from when you create it (or from its start date in Scheduler), so rate(1 hour) created at 10:17 runs at 11:17, 12:17 and so on.
  • Use cron when the clock matters: 9:00 on weekdays, the first of the month, every quarter-hour exactly on :00, :15, :30, :45.
  • Use at() (Scheduler only) for a single future run: a reminder, a trial ending, a promotion switching off. One-time schedules still count towards your schedule quota after they run, so delete them or set ActionAfterCompletion to DELETE.

Every schedule type runs with 60-second precision: a job set for 1:00 starts somewhere between 1:00:00 and 1:00:59. If you need several schedules not to hit a downstream system at the same second, a flexible time window in Scheduler (Mode FLEXIBLE, up to 1,440 minutes) spreads the invocations out on purpose.

EventBridge rules vs EventBridge Scheduler

EventBridge has two ways to run something on a schedule, and the question "EventBridge rules vs Scheduler" comes up because both accept the same cron syntax. Scheduled rules are now the legacy option; Scheduler is the better choice for anything new you build.

FeatureEventBridge SchedulerScheduled rule (legacy)
Time zonesAny IANA zone, daylight saving handledUTC only
One-time runsYes, at()No
TargetsTemplated targets (Lambda, SQS, SNS, Step Functions, ECS and more) plus universal targets for API operations across hundreds of servicesThe standard EventBridge rule targets
Start and end datesYesNo (enable and disable by hand)
Flexible time windowYesNo
Retries and dead-letter queueYes, per schedule (by default up to 185 retries over 24 hours)Yes, per target
Permissions to call the targetAn execution role you give the scheduleLambda: a resource-based permission on the function; some targets use a role
ScaleBuilt for very large numbers of schedules, grouped in schedule groupsLimited number of rules per event bus

If you already run rules that work, there is no rush. When you next touch one (a time zone complaint is the usual trigger), recreate it as a schedule and delete the rule. If a rule is not firing at all, our EventBridge rule debugging guide walks through the checks.

Jake: "Is EventBridge just a cron job in the cloud, then?"

Ethan: "The Scheduler part is. Except there's no server to keep alive, it retries for you, and it knows what time it is in London."

Schedule a Lambda function: CLI, Python, Terraform and SAM

The most common target is a Lambda function. With Scheduler, you need an execution role that Scheduler can assume and that is allowed to invoke your function. Here is the whole set-up from the CLI:

  1. Create the role's trust policy in a file trust.json, so Scheduler can assume it:
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "Service": "scheduler.amazonaws.com" },
    "Action": "sts:AssumeRole",
    "Condition": { "StringEquals": { "aws:SourceAccount": "111122223333" } }
  }]
}
  1. Create the role and let it invoke the function:
aws iam create-role --role-name scheduler-cart-emails --assume-role-policy-document file://trust.json
aws iam put-role-policy --role-name scheduler-cart-emails --policy-name invoke-cart-emails \
  --policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"lambda:InvokeFunction","Resource":"arn:aws:lambda:eu-west-2:111122223333:function:cart-emails"}]}'
  1. Create the schedule in local time:
aws scheduler create-schedule --name cart-emails-london \
  --schedule-expression "cron(0 9 ? * MON-FRI *)" --schedule-expression-timezone "Europe/London" \
  --flexible-time-window '{"Mode": "OFF"}' \
  --target '{"Arn": "arn:aws:lambda:eu-west-2:111122223333:function:cart-emails", "RoleArn": "arn:aws:iam::111122223333:role/scheduler-cart-emails", "Input": "{\"market\": \"uk\"}"}'

On Windows PowerShell, inline JSON quoting is painful; put the target in a file and pass --target file://target.json instead. If the create call fails with an access error about passing the role, the identity you are using needs iam:PassRole on it; our PassRole guide explains that error.

The same schedule in Python (boto3):

import boto3

scheduler = boto3.client("scheduler", region_name="eu-west-2")
scheduler.create_schedule(
    Name="cart-emails-london",
    ScheduleExpression="cron(0 9 ? * MON-FRI *)",
    ScheduleExpressionTimezone="Europe/London",
    FlexibleTimeWindow={"Mode": "OFF"},
    Target={
        "Arn": "arn:aws:lambda:eu-west-2:111122223333:function:cart-emails",
        "RoleArn": "arn:aws:iam::111122223333:role/scheduler-cart-emails",
        "Input": '{"market": "uk"}',
    },
)

In Terraform:

resource "aws_scheduler_schedule" "cart_emails" {
  name                         = "cart-emails-london"
  schedule_expression          = "cron(0 9 ? * MON-FRI *)"
  schedule_expression_timezone = "Europe/London"

  flexible_time_window {
    mode = "OFF"
  }

  target {
    arn      = aws_lambda_function.cart_emails.arn
    role_arn = aws_iam_role.scheduler.arn
  }
}

In an AWS SAM template, the ScheduleV2 event type creates a Scheduler schedule for the function, and SAM builds the role for you:

  CartEmailsFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: app.handler
      Runtime: python3.13
      Events:
        WeekdayMornings:
          Type: ScheduleV2
          Properties:
            ScheduleExpression: "cron(0 9 ? * MON-FRI *)"
            ScheduleExpressionTimezone: Europe/London

Still on a legacy rule? The Lambda side is different: the rule has no role, so the function needs a resource-based permission for events.amazonaws.com:

aws events put-rule --name cart-emails --schedule-expression "cron(0 8 ? * MON-FRI *)"
aws lambda add-permission --function-name cart-emails --statement-id eventbridge-cart-emails \
  --action lambda:InvokeFunction --principal events.amazonaws.com \
  --source-arn arn:aws:events:eu-west-2:111122223333:rule/cart-emails
aws events put-targets --rule cart-emails --targets "Id"="1","Arn"="arn:aws:lambda:eu-west-2:111122223333:function:cart-emails"

Once it runs, the function's own logs show each invocation; if nothing appears there, our Lambda logging guide covers the missing-log cases. For ECS tasks on a schedule, our ECS scheduled task guide has the RunTask role and network settings.

Create a schedule with boto3 (recurring and one-time)

EventBridge Scheduler has its own boto3 client, scheduler. A recurring schedule in a local time zone, with retries and a dead-letter queue, and a one-time schedule that deletes itself after it runs:

import boto3, json

scheduler = boto3.client("scheduler", region_name="eu-west-2")
target = {
    "Arn": "arn:aws:lambda:eu-west-2:111122223333:function:daily-report",
    "RoleArn": "arn:aws:iam::111122223333:role/scheduler-invoke",
    "Input": json.JSONEncoder().encode({"report": "sales"}),
    "RetryPolicy": {"MaximumRetryAttempts": 3, "MaximumEventAgeInSeconds": 3600},
    "DeadLetterConfig": {"Arn": "arn:aws:sqs:eu-west-2:111122223333:scheduler-dlq"},
}

scheduler.create_schedule(                                     # every weekday at 07:00 London time
    Name="daily-sales-report",
    ScheduleExpression="cron(0 7 ? * MON-FRI *)",
    ScheduleExpressionTimezone="Europe/London",
    FlexibleTimeWindow={"Mode": "OFF"},
    Target=target,
)

scheduler.create_schedule(                                     # once, then the schedule deletes itself
    Name="black-friday-reminder",
    ScheduleExpression="at(2026-11-27T08:00:00)",
    ScheduleExpressionTimezone="America/New_York",
    FlexibleTimeWindow={"Mode": "OFF"},
    ActionAfterCompletion="DELETE",
    Target=target,
)

Points worth knowing about these parameters. FlexibleTimeWindow is required: OFF runs at the exact time, while FLEXIBLE with MaximumWindowInMinutes lets Scheduler spread invocations across a window, which helps when hundreds of schedules would otherwise fire at the same second. at() expressions create one-time schedules, and ActionAfterCompletion="DELETE" removes them afterwards so they do not pile up against your quota. StartDate and EndDate bound a recurring schedule, and State lets you create it disabled. Schedules live in the default group unless you pass GroupName, which is handy for tagging and deleting related schedules together.

The IAM role EventBridge Scheduler needs

Unlike rules, which use resource-based permissions on the Lambda function, Scheduler always assumes an execution role you provide. The role needs a trust policy for the Scheduler service and permission to call the target:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "Service": "scheduler.amazonaws.com" },
    "Action": "sts:AssumeRole",
    "Condition": { "StringEquals": { "aws:SourceAccount": "111122223333" } }
  }]
}

Then attach a permissions policy allowing lambda:InvokeFunction on the function's ARN (or ecs:RunTask plus iam:PassRole for ECS targets, states:StartExecution for Step Functions, sqs:SendMessage for queues and the dead-letter queue). Keep one role per application rather than one per schedule; a single role allowed to invoke a small set of functions is easier to review than dozens of near-identical roles. A schedule that is "enabled but never runs" is very often a role that cannot be assumed or lacks the target permission; the failures appear in the schedule's CloudWatch metrics and in the dead-letter queue, if you configured one.

Moving a schedule from an EventBridge rule to Scheduler

Old scheduled rules keep working, but moving them to Scheduler gives you time zones, retries per schedule and one-time schedules. The move is low risk if you do it in this order:

  1. Create the execution role for Scheduler with permission to call the same target.
  2. Create the schedule disabled, with the same target and input; convert the UTC cron to local time with ScheduleExpressionTimezone if that is what the job really wants.
  3. Disable the rule and enable the schedule in one change window, so the job never runs twice or zero times.
  4. Watch one or two runs, then delete the old rule and the Lambda permission that allowed EventBridge to invoke the function.

Schedule names can be up to 64 characters, expressions up to 256 and descriptions up to 512, so long, descriptive names from old rules may need shortening.

EventBridge Scheduler in CloudFormation

Resources:
  DailyReportSchedule:
    Type: AWS::Scheduler::Schedule
    Properties:
      ScheduleExpression: cron(0 7 ? * MON-FRI *)
      ScheduleExpressionTimezone: Europe/London
      FlexibleTimeWindow:
        Mode: "OFF"
      Target:
        Arn: !GetAtt ReportFunction.Arn
        RoleArn: !GetAtt SchedulerRole.Arn
        Input: '{"report": "sales"}'

The template assumes two other resources in the same stack: the Lambda function (ReportFunction) and an IAM role (SchedulerRole) whose trust policy and permissions match the previous section. Defining all three together keeps the ARNs in step and lets a stack update change the time without touching the console. In AWS SAM, the shortcut is an event of type ScheduleV2 on the function, which creates the Scheduler schedule and its role for you; the older Schedule event type creates an EventBridge rule instead, which has no time zone support.

Quote "OFF" in YAML: unquoted, YAML reads OFF as the boolean false and the template fails validation. The same applies in SAM templates. For Terraform, the matching resource is aws_scheduler_schedule, with the same fields in snake case.

AWS Glue cron expressions

AWS Glue schedules for jobs and crawlers use the same six-field cron syntax, with two differences worth remembering: times are always in UTC, and the minimum precision is 5 minutes, so expressions that would run more often are rejected. Day-of-month and day-of-week follow the same rule (one of them must be ?), and L and W work as in EventBridge.

Glue scheduleExpression
Every day at 10:00 UTCcron(0 10 * * ? *)
Weekdays at 18:00 UTCcron(0 18 ? * MON-FRI *)
First day of each month at 08:00 UTCcron(0 8 1 * ? *)
Every 15 minutescron(0/15 * * * ? *)
Every 5 minutes, weekdays 08:00-17:55 UTCcron(0/5 8-17 ? * MON-FRI *)

Glue triggers can also start several jobs and crawlers at once, and conditional triggers can start a job when others finish, so a nightly pipeline often needs just one scheduled trigger at the start and conditional triggers after it, rather than a separate cron for each step. That also removes guesswork about how long each step takes.

If a Glue job must follow local time with daylight saving, trigger it from EventBridge Scheduler (which supports time zones) instead of a Glue scheduled trigger, or adjust the UTC hour twice a year.

Systems Manager cron and rate expressions

Systems Manager uses cron and rate expressions for maintenance windows and State Manager associations, including the ones Quick Setup creates for patching and inventory. The familiar forms work (cron(0 2 ? * SUN *), rate(1 day)), plus a few Systems Manager extras:

  • One-time runs with a UTC timestamp, such as at(2026-11-01T02:00:00).
  • Schedule offsets for cron expressions: a cron for the second Tuesday of the month plus an offset of 2 runs on the Thursday after, which is how many teams schedule patching a couple of days after Microsoft's monthly updates.
  • Time zones for maintenance windows, set separately from the expression, with start and end dates that only bound when the window is active.
  • Associations and offsets: an association with an offset needs the option that stops it running immediately at creation (--apply-only-at-cron-interval).

A typical patching setup ties these together: a maintenance window on cron(0 2 ? * TUE#2 *) (the second Tuesday at 02:00) with an offset of a few days and a two-hour duration, targeting instances by tag, running the patch baseline task. The # syntax ("the second Tuesday") works in day-of-week here as it does in EventBridge.

Schedules cron cannot express (and what to do instead)

Some everyday schedules have no single AWS cron expression. The common ones and their workarounds:

  • Every other week: cron has no "every second Monday" concept. Use rate(14 days) with a start date on the right Monday (Scheduler's StartDate), or run weekly and have the code skip odd weeks by checking the ISO week number.
  • Last business day of the month: LW in day-of-month means the last weekday, which covers most cases (cron(0 18 LW * ? *)); public holidays still need a check in code.
  • The last Friday of the month: 6L in day-of-week (cron(0 17 ? * 6L *)), since Friday is day 6 when Sunday is 1.
  • Different times on different days (9:00 on weekdays, 11:00 at weekends): two schedules pointing at the same target.
  • Every 90 seconds: below a minute, schedules cannot help; run a loop inside a long-lived task, or a Step Functions workflow with a Wait state.
  • Business hours only: a range in the hours field, such as cron(0/30 9-17 ? * MON-FRI *) for every half hour from 9:00 to 17:30.

Monitoring scheduled jobs

A schedule that silently stops is worse than one that fails loudly, so watch both the schedule and the job. For EventBridge rules, the AWS/Events metrics TriggeredRules, Invocations and FailedInvocations show whether the rule fired and whether the target accepted it. For EventBridge Scheduler, the AWS/Scheduler metrics include attempts, target errors, throttles, dropped invocations and invocations sent to the dead-letter queue. Alarm on failures and on dead-letter queue depth.

Then monitor the job itself: a Lambda function's Errors metric, or a "heartbeat" custom metric the job publishes when it finishes, with an alarm that fires when the metric is missing for longer than the schedule interval. That last alarm catches the case no other check does: the schedule fired, the job started, and it quietly did nothing. Our CloudWatch alarm guide explains how to treat missing data so the heartbeat alarm behaves.

Jake: "The nightly export hadn't run for a week and nobody noticed."

Ethan: "Add a heartbeat metric and alarm when it goes missing. Silence should page someone."

Testing a schedule before you trust it

  1. Run the target by hand with the exact input the schedule will send (for Lambda, a test event with the same JSON).
  2. Create a one-time copy with an at() expression a few minutes in the future, the same target and role, and watch it fire.
  3. Check the time zone by reading the next trigger dates in the console for a date after the next daylight saving change.
  4. Break it on purpose: point a test schedule at a function that throws, and confirm retries and the dead-letter queue behave as you expect.

Delete the test schedules afterwards, or give them ActionAfterCompletion set to delete, so forgotten copies do not run real jobs months later. For jobs with side effects, such as emails or payments, test against a staging target or with a dry-run flag in the input, never against production data on the first try.

How to run a cron job in AWS: the options

JobBest fitWhy
Short script, under 15 minutesScheduler + LambdaNo servers, pay per run
Long job or containerScheduler + ECS task on FargateHours of runtime, any language
Several steps with retriesScheduler + Step FunctionsVisual workflow, per-step error handling
ETL on a data lakeGlue scheduled triggerBuilt into Glue jobs and crawlers
Patching and server tasksSystems Manager maintenance windowTargets fleets of instances
Existing script on one servercrontab on EC2Simplest lift-and-shift, but tied to that instance

For anything new, EventBridge Scheduler with a managed target is the default: no server to keep running, time zone support, retries, dead-letter queues and up to a million schedules. Keep crontab for scripts that genuinely need to run on one particular machine. If a scheduled ECS task does not start, our ECS scheduled task guide covers the usual causes.

"ScheduleExpression is not valid" and schedules that do not run

A rule with a bad expression fails with Parameter ScheduleExpression is not valid.; Scheduler returns a ValidationException about the schedule expression. The cause is almost always one of these:

  • Five fields copied from Linux crontab: 0 9 * * 1-5. AWS needs six fields and the cron( ) wrapper.
  • * in both day fields: cron(0 9 * * MON-FRI *). Make one of them ?: cron(0 9 ? * MON-FRI *).
  • A seconds field from Spring or Quartz: 0 0 9 * * ?. EventBridge has no seconds; drop the first field and add a year.
  • Sunday as 0: day-of-week runs 1-7 with 1 = Sunday; 0 is not valid. Use names (SUN, MON-FRI) and the numbering problem disappears.
  • Plural or singular mismatch in a rate: rate(1 minutes), rate(5 minute).
  • Two # terms: 2#1,6#3 in day-of-week is rejected; split it into two schedules.
  • Anything faster than a minute: there is no way to express it.

A subtler mistake does not fail at all: numbered weekdays copied from Linux. In Linux cron, 1-5 is Monday to Friday; in EventBridge, 1-5 is Sunday to Thursday. The expression saves fine and runs on the wrong days. If a schedule saves but does not seem to run:

  1. Check it is enabled, and its start and end dates (Scheduler) include today.
  2. Check the time zone: is it running at the right time in UTC instead?
  3. Look at the AWS/Scheduler metrics in CloudWatch: InvocationAttemptCount at zero means it never fired; TargetErrorCount above zero means it fired and the target call failed (usually the execution role).
  4. Check the target's own logs (the Lambda log group, the ECS stopped tasks) for what happened after the call.
  5. Attach a dead-letter queue to the schedule so failed invocations land somewhere you can read.

For a function that is invoked but fails because Scheduler's role cannot be used, our role cannot be assumed guide shows the trust policy checks.

Jake: "What if I need it every ten seconds?"

Ethan: "Then a schedule is the wrong tool. One minute is the floor. Run something that loops, or rethink why it needs ten seconds."

Linux cron, GitHub Actions, Spring and Azure: how they convert

Most cron expressions you find online are in another dialect. The differences in one place:

DialectFieldsSunday"Weekdays at 9:00"
AWS EventBridge6: min hour dom month dow year1 (or SUN)cron(0 9 ? * MON-FRI *)
Linux crontab5: min hour dom month dow0 (7 in many versions)0 9 * * 1-5
GitHub Actions schedule5, POSIX style, always UTC, shortest interval 5 minutes00 9 * * 1-5
Spring @Scheduled6: sec min hour dom month dow0 or 7 (or SUN)0 0 9 * * MON-FRI
Azure Functions timer trigger6 (NCRONTAB): sec min hour day month dow00 0 9 * * 1-5
Quartz6 or 7: sec min hour dom month dow [year]1 (or SUN)0 0 9 ? * MON-FRI

To convert anything to EventBridge: drop a leading seconds field if there is one, put ? in whichever day field you are not using, add * as the year, wrap it in cron( ), and replace numbered weekdays with names. A Linux */5 * * * * becomes cron(0/5 * * * ? *); a Spring 0 30 6 * * * becomes cron(30 6 * * ? *).

What EventBridge Scheduler costs

Scheduler is billed per invocation: the first 14 million invocations each month are free (counted across Regions), then $1.00 per million. To put that in perspective for your own jobs:

  • A schedule every minute is about 43,200 invocations a month: free.
  • A hundred schedules every five minutes is about 864,000: free.
  • Twenty million invocations in a month cost (20 - 14) × $1.00 = $6.00.

The schedule is rarely the expensive part. What it starts (Lambda duration, Fargate tasks, Step Functions transitions) is what shows up on your cloud bill, so a job that runs every minute "just in case" is worth questioning on cost alone. For multi-step jobs, a schedule that starts a Step Functions workflow keeps the retry and branching logic out of your function.

Frequently asked questions

How do I validate an AWS cron expression?

Type it into the EventBridge console to see the next trigger dates, or create a disabled schedule with the CLI; an invalid expression fails with a ValidationException.

Why does an online cron generator's expression fail in AWS?

Most generators output Linux cron (five fields) or Quartz cron (with seconds); AWS needs six fields, minutes to year, with ? in one day field.

What cron syntax does AWS Glue use?

The same six-field cron, always in UTC, with a minimum precision of 5 minutes, for example cron(15 12 * * ? *).

Does Systems Manager use cron expressions?

Yes, for maintenance windows and State Manager associations, plus rate expressions, one-time at() timestamps and schedule offsets.

How do I create an EventBridge schedule with boto3?

Call boto3.client("scheduler").create_schedule with Name, ScheduleExpression, FlexibleTimeWindow={"Mode": "OFF"} and a Target with Arn and RoleArn.

What IAM role does EventBridge Scheduler need?

A role trusted by scheduler.amazonaws.com with permission to call the target, such as lambda:InvokeFunction on the function.

How do I run a one-time schedule in AWS?

Use an at() expression in EventBridge Scheduler, such as at(2026-11-27T08:00:00), and set ActionAfterCompletion to DELETE.

Can I move an EventBridge rule schedule to EventBridge Scheduler?

Yes: create a disabled schedule with the same target and an execution role, then disable the rule and enable the schedule together.

Does AWS SAM support EventBridge Scheduler?

Yes, with the ScheduleV2 event type on a function; the older Schedule event type creates an EventBridge rule without time zone support.

How do I schedule something every other week in AWS?

Use rate(14 days) with a StartDate on the right day in EventBridge Scheduler, or run weekly and skip alternate weeks in code.

How do I run a job on the last Friday of the month?

Use 6L in the day-of-week field, for example cron(0 17 ? * 6L *), with ? in day-of-month.

How do I know if my scheduled job stopped running?

Alarm on the schedule's failure metrics and on a heartbeat metric the job publishes, treating missing data as a breach.

What is the best way to run a cron job in AWS?

EventBridge Scheduler with Lambda for short jobs, an ECS task for long ones, or Step Functions for multi-step jobs.

What is the format of an AWS EventBridge cron expression?

cron(minutes hours day-of-month month day-of-week year), six fields separated by spaces, for example cron(0 9 ? * MON-FRI *).

What is the EventBridge cron expression for every 5 minutes?

cron(0/5 * * * ? *), or the simpler rate(5 minutes).

What is the EventBridge cron expression for every hour?

cron(0 * * * ? *) runs on the hour; rate(1 hour) runs every hour from when the schedule was created.

What is the cron expression for every day at midnight?

cron(0 0 * * ? *), in UTC unless your Scheduler schedule has a time zone.

Why does EventBridge need a question mark in the cron expression?

Day-of-month and day-of-week overlap, so one of them must be ? (any) whenever the other has a value or *.

Does EventBridge cron use UTC?

Scheduled rules always use UTC. EventBridge Scheduler uses UTC by default, or any IANA time zone you set with ScheduleExpressionTimezone.

How do I set a time zone for an EventBridge schedule?

Use EventBridge Scheduler and pass an IANA name such as Europe/London or Australia/Sydney with --schedule-expression-timezone.

Does EventBridge Scheduler handle daylight saving time?

Yes. A time that does not exist on the spring-forward night is skipped, and a repeated hour on the fall-back night runs once.

Can EventBridge run a schedule every 30 seconds?

No. The shortest interval is 1 minute and there is no seconds field; use a looping process or workflow for sub-minute work.

What is the difference between EventBridge rules and Scheduler?

Scheduler adds time zones, one-time schedules, start and end dates, flexible windows and many more targets; scheduled rules are the legacy, UTC-only option.

Is Sunday 0 or 1 in EventBridge cron?

Sunday is 1 and Saturday is 7, so Monday to Friday is 2-6 or MON-FRI. Names are safer than numbers.

Why is my EventBridge cron expression not valid?

Usually five fields instead of six, * in both day fields, a seconds field, Sunday written as 0, or a rate with the wrong plural.

What is the difference between rate and cron in EventBridge?

Rate repeats at a fixed interval from the start; cron runs at specific clock times and dates.

How do I schedule a Lambda function with EventBridge?

Create an EventBridge Scheduler schedule with the function as the target and an execution role allowed to call lambda:InvokeFunction.

How do I run a schedule only once?

Use a Scheduler one-time schedule with at(yyyy-mm-ddThh:mm:ss) and a time zone, and set ActionAfterCompletion to DELETE.

How much does EventBridge Scheduler cost?

The first 14 million invocations a month are free, then $1.00 per million invocations.

Is EventBridge the same as CloudWatch Events?

Yes, CloudWatch Events was renamed EventBridge; the scheduled rules you made there are EventBridge rules today.

How do I test an EventBridge cron expression?

Check the next ten run times the console shows before you save, and compare them with what you expect in your time zone.

Why does my schedule run on the wrong days?

Numbered weekdays copied from Linux cron: 1-5 there is Monday to Friday, but in EventBridge it is Sunday to Thursday.

For readers preparing for an AWS certification

We have been covering AWS certifications alongside our fixes, so here is a short extra for readers studying for one. If you only came for the fix, you already have it; feel free to skip this part.

Exams that test this topic: Developer – Associate (DVA-C03 from December 1, 2026) • CloudOps Engineer – Associate (SOA-C03) • Solutions Architect – Associate (SAA-C03).

Here are the exam-style questions this topic tends to raise, with the reasoning you need to answer them:

1. A Lambda function must run at 9:00 local time in London every weekday, all year. What is the simplest setup?

An EventBridge Scheduler schedule with cron(0 9 ? * MON-FRI *) and the Europe/London time zone.

2. Which time zone do legacy EventBridge scheduled rules use?

UTC only.

3. What is the shortest interval an EventBridge schedule can run at?

One minute.

4. A job must run once at a future date and time. Which EventBridge feature fits?

An EventBridge Scheduler one-time schedule with an at() expression.

5. What does EventBridge Scheduler need to invoke a Lambda function?

An execution role it can assume that allows lambda:InvokeFunction.

6. Which expression runs every 5 minutes?

rate(5 minutes), or cron(0/5 * * * ? *).

7. In an EventBridge cron expression, what must one of the two day fields contain?

A ? (question mark).

Exam codes and dates change often. Our AWS certification changes page keeps track of what is current.

πŸ“š ALSO READ

Where to go next, from Lambda roles to Step Functions:

📌 Bookmark this; the examples table is the part you will come back for.

The abandoned-cart emails now leave at 9:00 London time every weekday, summer and winter. Ethan added a second schedule with the same expression and Australia/Sydney for the new Australian store, which took two minutes instead of a lesson in time zone arithmetic. Jake's note for the team wiki: "Six fields, one question mark, names not numbers, and always set the time zone."

📌 If you keep one line from this page

EventBridge cron is cron(minutes hours day-of-month month day-of-week year) with a ? in one day field and 1 = Sunday; use EventBridge Scheduler with an IANA time zone so 9:00 means 9:00 where you are.

rate() for intervals · at() for one-off runs · check the next-ten-runs preview before saving.

Revision note. Written October 19, 2026. Cron has been telling computers when to wake up since the 1970s; AWS only added a sixth field and a question mark.

Related