AWS EventBridge Scheduler — A Complete Guide
AWS has two cron-style scheduling services: the legacy EventBridge Rules (always UTC, simpler) and the newer EventBridge Scheduler (per-schedule timezones, flexible time windows, scales to millions of schedules). This guide covers both, plus the IAM, DLQ, and monitoring patterns you need for production.
Updated
Rules vs Scheduler
AWS has two distinct services for scheduled invocations:
EventBridge Rules (formerly CloudWatch Events). The original service. Each “rule” can be scheduled with a cron or rate expression and points at one or more targets. Always UTC. ~100 rules per region per account. Console: EventBridge → Rules.
EventBridge Scheduler. Released late 2022. Designed for scheduled-job-at-scale. Each “schedule” is a separate resource with its own IAM role, timezone, time window, and DLQ. Limits are in the millions. Console: EventBridge → Scheduler → Schedules.
| Feature | Rules | Scheduler |
|---|---|---|
| Cron + rate syntax | ✓ | ✓ |
| Per-schedule timezone | — | ✓ |
| One-time schedules | — | ✓ |
| Flexible time windows | — | ✓ |
| Built-in DLQ | — | ✓ (per schedule) |
| Multiple targets per schedule | ✓ | — |
| Scale ceiling | ~100/region | millions |
For new work, use Scheduler. It has every feature Rules has plus the per-schedule timezone and DLQ that Rules is missing. The only reason to use Rules today is if you need multiple targets per rule (Scheduler is one-target-per-schedule) or you’re maintaining existing infrastructure that uses Rules.
The rest of this guide focuses on Scheduler with notes about Rules where they differ.
Schedule expressions
Both services accept three expression types:
cron(...) — 6-field with year:
cron(0 12 * * ? *) # daily at 12:00 UTC
cron(0 9 ? * MON-FRI *) # weekdays at 09:00 UTC
cron(*/5 * * * ? *) # every 5 minutes
cron(0 0 L * ? *) # last day of every month at 00:00
Fields: minute, hour, day-of-month, month, day-of-week, year. Exactly one of day-of-month or day-of-week must be ? — the placeholder meaning “no specific value.” Sunday is 1, Saturday is 7. See the AWS EventBridge dialect reference for the full syntax including L, W, and #.
rate(...) — fixed interval, no clock alignment:
rate(5 minutes)
rate(1 hour)
rate(7 days)
Use rate() when you want “every N units from when this was created” rather than “every N units aligned to clock time.” Minimum granularity is 1 minute.
at(...) — one-time schedule (Scheduler only):
at(2026-12-31T23:59:59)
Useful for scheduled future tasks (a reminder, a delayed action, a planned cutover). The schedule fires once and then can be configured to auto-delete.
Targets and IAM
A target is what gets invoked when the schedule fires. Via its universal target, EventBridge Scheduler can invoke over 6,000 API operations across more than 270 AWS services. The most common:
- Lambda function —
Invoke - ECS task —
RunTask - Step Functions state machine —
StartExecution - SNS topic —
Publish - SQS queue —
SendMessage - EventBridge event bus —
PutEvents
Every schedule needs an IAM role with permission to invoke its target. The role must trust scheduler.amazonaws.com:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "scheduler.amazonaws.com" },
"Action": "sts:AssumeRole"
}]
}
And it must have permission to call the target operation:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": "lambda:InvokeFunction",
"Resource": "arn:aws:lambda:us-east-1:123456789012:function:my-function"
}]
}
Common mistake: granting lambda:InvokeFunction to a Lambda but not granting scheduler.amazonaws.com permission to assume the role. Both sides of the trust relationship have to be set.
Terraform example:
resource "aws_scheduler_schedule" "nightly_report" {
name = "nightly-report"
schedule_expression = "cron(0 0 * * ? *)"
schedule_expression_timezone = "America/New_York"
flexible_time_window {
mode = "OFF"
}
target {
arn = aws_lambda_function.report.arn
role_arn = aws_iam_role.scheduler_role.arn
}
}
Timezones
EventBridge Scheduler supports per-schedule timezones via ScheduleExpressionTimezone. Pass any IANA timezone name:
ScheduleExpression: cron(0 9 ? * MON-FRI *)
ScheduleExpressionTimezone: America/Los_Angeles
This runs at 9 AM Pacific time, automatically adjusting for DST.
EventBridge Rules does not support per-rule timezones. Everything is UTC. To run at 9 AM Pacific year-round, you’d need two rules (PST and PDT) gated by date — same workaround as GitHub Actions, equally awkward. If you need timezone-aware Rules, migrate to Scheduler.
Flexible time windows
EventBridge Scheduler supports “flexible time windows” — instead of firing exactly at the scheduled time, fire within a window:
FlexibleTimeWindow: { Mode: FLEXIBLE, MaximumWindowInMinutes: 15 }
With this set, a schedule of cron(0 9 * * ? *) will fire anywhere between 9:00 and 9:15 UTC. AWS chooses the exact time within the window to spread load across customers — useful when you have thousands of similar schedules that would otherwise stampede the same target.
Default mode is OFF (fire exactly at the scheduled time). Set FLEXIBLE for any schedule where ±15 minutes is acceptable and you’d benefit from jitter.
Dead-letter queues
When a target invocation fails (Lambda errored, ECS task wouldn’t start, IAM role lost permission), EventBridge retries with exponential backoff for up to 24 hours. If still failing after that, the event is discarded — silently.
Configure a DLQ on every production schedule:
resource "aws_scheduler_schedule" "nightly_report" {
# ...
target {
arn = aws_lambda_function.report.arn
role_arn = aws_iam_role.scheduler_role.arn
dead_letter_config {
arn = aws_sqs_queue.scheduler_dlq.arn
}
}
}
The DLQ is an SQS queue you create. Failed events are sent there with the original payload + failure metadata. Set up a CloudWatch alarm on the queue’s ApproximateNumberOfMessagesVisible to alert when failures pile up.
For EventBridge Rules, the DLQ is configured per-target in the rule definition. Same SQS-queue pattern.
Monitoring and debugging
CloudWatch metrics emitted by EventBridge Scheduler (namespace AWS/Scheduler):
Invocations— count of successful target invocationsInvocationAttemptCount— total attempts (includes retries)InvocationsFailedToBeSentToDeadLetterQueue— bad sign, the DLQ itself is failingInvocationDroppedCount— events that exhausted retries and weren’t DLQ’d
Set CloudWatch alarms on:
- Failures-to-DLQ > 0 (an actual incident)
- Invocations dropping to 0 when you expect activity (the schedule isn’t firing)
For ad-hoc debugging:
- Verify the cron expression — paste it into the parser with the EventBridge dialect selected. Check next-run times. Remember to add
cron(...)wrapper. - Check IAM — the schedule’s role must trust
scheduler.amazonaws.comAND have permission to invoke the target. - Check the schedule’s recent history — Scheduler shows recent invocation attempts in the console.
- Test the target manually — invoke the Lambda or ECS task directly to confirm it works outside the scheduler. If it works manually but not via Scheduler, the issue is IAM or input shape.
- Check the DLQ — failed events with detailed error info are sitting there.
For one-off testing of a not-yet-deployed schedule, use at(...) with a near-future time to fire once and observe.
Frequently asked questions
What's the difference between EventBridge Rules and EventBridge Scheduler?
What cron syntax does EventBridge use?
Can I use rate() instead of cron()?
How do I run an EventBridge schedule in a specific timezone?
What happens when an EventBridge target fails?
Related
Every 5 Minutes
`*/5 * * * ? *` — high-frequency invocations on EventBridge.
PatternDaily at Midnight
`0 0 * * ? *` — UTC midnight daily.
ToolCron Parser
Validate an EventBridge expression and see the next runs.
ToolMulti-Dialect Converter
Convert a Unix or Quartz expression into the EventBridge format.
GuideCron Dialect Comparison
How EventBridge cron differs from Unix, Quartz, Kubernetes, and others.