Skip to content

Cron schedules

A cron is an HTTP request that Delay sends on a schedule you write as a cron expression. Use one for anything that repeats: a health check every 5 minutes, a backup every night, a report every Monday.

0 3 * * * every day at 03:00 UTC
*/5 * * * * every 5 minutes
0 9 * * 1-5 weekdays at 09:00 UTC

To create one, see Schedule a recurring request.

Syntax

An expression has exactly five fields, separated by spaces:

┌───────────── minute 0-59
│ ┌─────────── hour 0-23
│ │ ┌───────── day of month 1-31
│ │ │ ┌─────── month 1-12 or JAN-DEC
│ │ │ │ ┌───── day of week 0-7 or SUN-SAT (0 and 7 are both Sunday)
│ │ │ │ │
* * * * *

Month and day names are case-insensitive (MON, mon and Mon all work).

CharacterMeaningExample
*Every value* * * * *: every minute
,A list0,30 * * * *: at minute 0 and 30
-A range0 9-17 * * *: every hour from 09:00 to 17:00
/A step*/15 * * * *: every 15 minutes
LLast (day of month), or last weekday of that kind (day of week)0 0 L * *: last day of the month. 0 17 * * 5L: last Friday
WNearest weekday to a day of month0 9 15W * *: the weekday closest to the 15th
#Nth weekday of the month0 9 * * 1#1: first Monday
?Same as *, day of month or day of week only0 9 ? * 1

You can also use these shortcuts (case-insensitive):

ShortcutSame asRuns
@hourly0 * * * *At the start of every hour
@daily, @midnight0 0 * * *Every day at 00:00 UTC
@weekly0 0 * * 0Sunday at 00:00 UTC
@monthly0 0 1 * *The 1st of the month at 00:00 UTC
@yearly, @annually0 0 1 1 *January 1st at 00:00 UTC

What gets rejected

Delay checks the expression when you save the cron:

  • Fewer or more than five fields. There is no seconds field and no year field, so the shortest interval is one minute.
  • @reboot, and any other shortcut not in the table above.
  • An expression that never matches a real date, such as 0 0 30 2 * (February 30th).

The errors read “The cron expression is not a valid cron expression.” and “The cron expression never produces a valid run time.”

Things that surprise people

  • Day of month and day of week together means either. 0 9 1 * 1 runs on the 1st of the month and on every Monday, not only on Mondays that fall on the 1st. For “first Monday” use 0 9 * * 1#1.
  • Steps restart every hour (or day). */7 * * * * runs at :49 and :56, then at :00 of the next hour, a 4-minute gap. Steps that divide evenly into 60 (5, 10, 15, 20, 30) don’t have this problem.
  • A step can’t span hours. */90 * * * * is accepted but does not run every 90 minutes. For a 90-minute rhythm, create two crons: 0 0-21/3 * * * and 30 1-22/3 * * *.

Timezone: always UTC

Delay evaluates every expression in UTC. There is no timezone setting on a cron. The Timezone in Settings > Profile only changes how dates are displayed: the Next run time in the dashboard is shown in that timezone (or your browser’s, if you haven’t set one), while the expression itself is always UTC.

To run at a local time, convert it to UTC. Remember that UTC has no daylight saving time, so a fixed UTC time moves by an hour locally when your clocks change:

You wantUTC in winterUTC in summer
09:00 in New York0 14 * * * (EST, UTC-5)0 13 * * * (EDT, UTC-4)
09:00 in London0 9 * * * (GMT)0 8 * * * (BST, UTC+1)
09:00 in Berlin0 8 * * * (CET, UTC+1)0 7 * * * (CEST, UTC+2)

If the exact local hour matters, update the expression when the clocks change, or pick a time that works with both offsets.

When a cron runs

  • Once a minute. Delay checks for due crons every minute. A run starts in its scheduled minute or shortly after, never before.
  • The first run is the next match. A new cron doesn’t run when you save it. A */5 * * * * cron created at 12:03 first runs at 12:05 UTC. To try it straight away, use Run now on the cron’s page.
  • Run now sends one request immediately without changing the schedule. It only works while the cron is active, and it counts toward your monthly usage.
  • Paused crons don’t run. When you start a paused cron again, it waits for the next matching time. Runs missed while it was paused are not made up.
  • Missed runs are not replayed. If a cron’s time passed without a run (for example during an outage), it runs once when Delay catches up and then continues on its normal schedule.

Overlapping runs

Delay doesn’t wait for the previous run to finish. If a request takes longer than the interval, the next run starts anyway and the two overlap. For example, an every-minute cron with a 120-second Request timeout can have two requests in flight against a slow endpoint.

To avoid it, keep the timeout below the interval, or make your endpoint safe to run twice (see Write endpoints Delay can call).

What Delay sends

SettingDefaultNotes
MethodGETGET, POST, PUT, PATCH or DELETE
Request timeout30 seconds1 to 300 seconds. A run that takes longer fails with timeout.
HeadersnoneSent as you enter them
PayloadnoneA string, see below
Callback URLnoneDelay POSTs a summary after every run. See Callbacks.
Success assertionsnoneExtra checks on 2xx responses. See Success assertions.

A cron’s payload is a string, and Delay always sends it as JSON:

  • If the string is a JSON object or array, Delay sends that JSON. {"type": "full"} arrives as {"type":"full"}: it is parsed and re-encoded, so spacing can change.
  • Anything else, including plain text and bare JSON values like 42, is wrapped. The text nightly arrives as {"body":"nightly"}.
  • For GET, there is no body: the payload’s keys are added to the URL’s query string. The dashboard hides the payload field for GET.

The full list of headers and how each method carries a body is in What your endpoint receives.

Failures, retries and alerts

A run fails when the response isn’t 2xx, when no response arrives in time, or when an assertion fails. Every failed run records a failure reason.

Crons never retry. A failed run is recorded and the cron waits for its next scheduled time. Crons have no dead letter queue. If a request must be retried until it succeeds, send it as a job on a queue instead.

Alerting is off by default. Set Failure alerting to alert after 1, 2 or 3 consecutive failed runs (alert_threshold in the API). See Set up alerts.

Common patterns

ExpressionRuns (UTC)Use it for
* * * * *Every minuteCritical health checks
*/5 * * * *Every 5 minutesStandard health checks, polling
*/15 * * * *Every 15 minutesData freshness checks
*/30 * * * *Every 30 minutesFrequent syncs
0 * * * *Every hour, at :00Hourly aggregation, cache refresh
0 */6 * * *00:00, 06:00, 12:00, 18:00Periodic batch jobs
0 3 * * *Every day at 03:00Backups, nightly cleanup
30 8 * * 1-5Weekdays at 08:30Business-day reports
*/15 9-17 * * 1-5Every 15 minutes, 09:00 to 17:45, weekdaysBusiness-hours polling
0 9 * * 1Mondays at 09:00Weekly summaries
0 9 * * 1#1First Monday of the month at 09:00Monthly planning reminders
0 0 1 * *The 1st of every month at 00:00Monthly billing, invoices
0 0 L * *Last day of every month at 00:00Month-end close

The dashboard’s Quick presets… menu fills in the most common of these for you.

Spread out your schedules

If several crons hit the same server, don’t start them all at :00. Offset them by a few minutes:

0 * * * * cache refresh every hour at :00
5 * * * * search reindex every hour at :05
10 * * * * stats rollup every hour at :10

Do the same for nightly work: 0 1 * * *, 0 2 * * *, 0 3 * * * instead of three crons at midnight.

How often you run decides your usage

Every run counts as one request toward your team’s monthly quota, including failed runs and Run now. In a 31-day month:

ScheduleRequests per month
Every minute44,640
Every 5 minutes8,928
Every 15 minutes2,976
Every 30 minutes1,488
Every hour744
Once a day31

With 1,000 requests a month on the free plan, one hourly cron fits but one every 30 minutes doesn’t. Plan quotas and what happens at the limit: How usage is counted.

Next steps