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 minutes0 9 * * 1-5 weekdays at 09:00 UTCTo 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).
| Character | Meaning | Example |
|---|---|---|
* | Every value | * * * * *: every minute |
, | A list | 0,30 * * * *: at minute 0 and 30 |
- | A range | 0 9-17 * * *: every hour from 09:00 to 17:00 |
/ | A step | */15 * * * *: every 15 minutes |
L | Last (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 |
W | Nearest weekday to a day of month | 0 9 15W * *: the weekday closest to the 15th |
# | Nth weekday of the month | 0 9 * * 1#1: first Monday |
? | Same as *, day of month or day of week only | 0 9 ? * 1 |
You can also use these shortcuts (case-insensitive):
| Shortcut | Same as | Runs |
|---|---|---|
@hourly | 0 * * * * | At the start of every hour |
@daily, @midnight | 0 0 * * * | Every day at 00:00 UTC |
@weekly | 0 0 * * 0 | Sunday at 00:00 UTC |
@monthly | 0 0 1 * * | The 1st of the month at 00:00 UTC |
@yearly, @annually | 0 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 * 1runs on the 1st of the month and on every Monday, not only on Mondays that fall on the 1st. For “first Monday” use0 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 * * *and30 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 want | UTC in winter | UTC in summer |
|---|---|---|
| 09:00 in New York | 0 14 * * * (EST, UTC-5) | 0 13 * * * (EDT, UTC-4) |
| 09:00 in London | 0 9 * * * (GMT) | 0 8 * * * (BST, UTC+1) |
| 09:00 in Berlin | 0 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
| Setting | Default | Notes |
|---|---|---|
| Method | GET | GET, POST, PUT, PATCH or DELETE |
| Request timeout | 30 seconds | 1 to 300 seconds. A run that takes longer fails with timeout. |
| Headers | none | Sent as you enter them |
| Payload | none | A string, see below |
| Callback URL | none | Delay POSTs a summary after every run. See Callbacks. |
| Success assertions | none | Extra 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 textnightlyarrives 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 forGET.
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
| Expression | Runs (UTC) | Use it for |
|---|---|---|
* * * * * | Every minute | Critical health checks |
*/5 * * * * | Every 5 minutes | Standard health checks, polling |
*/15 * * * * | Every 15 minutes | Data freshness checks |
*/30 * * * * | Every 30 minutes | Frequent syncs |
0 * * * * | Every hour, at :00 | Hourly aggregation, cache refresh |
0 */6 * * * | 00:00, 06:00, 12:00, 18:00 | Periodic batch jobs |
0 3 * * * | Every day at 03:00 | Backups, nightly cleanup |
30 8 * * 1-5 | Weekdays at 08:30 | Business-day reports |
*/15 9-17 * * 1-5 | Every 15 minutes, 09:00 to 17:45, weekdays | Business-hours polling |
0 9 * * 1 | Mondays at 09:00 | Weekly summaries |
0 9 * * 1#1 | First Monday of the month at 09:00 | Monthly planning reminders |
0 0 1 * * | The 1st of every month at 00:00 | Monthly billing, invoices |
0 0 L * * | Last day of every month at 00:00 | Month-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 :005 * * * * search reindex every hour at :0510 * * * * stats rollup every hour at :10Do 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:
| Schedule | Requests per month |
|---|---|
| Every minute | 44,640 |
| Every 5 minutes | 8,928 |
| Every 15 minutes | 2,976 |
| Every 30 minutes | 1,488 |
| Every hour | 744 |
| Once a day | 31 |
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
- Schedule a recurring request: create, test, pause and delete a cron.
- POST /api/crons: create crons from your code.
- Runs and failure reasons: what each run records and why it failed.
- Success assertions: fail a run when a 200 isn’t enough.