The schedule argument of a DAG takes a cron expression, a preset such as "@daily", a timedelta
or a timetable object. The table below covers the usual cases. Times are in the DAG’s timezone,
which is UTC unless you
set another one.
Copy and paste
| Runs | schedule= |
|---|---|
| Every 15 minutes | "*/15 * * * *" |
| Every hour, on the hour | "@hourly" or "0 * * * *" |
| Every 2 hours | "0 */2 * * *" |
| Every day at 06:00 | "0 6 * * *" |
| Every day at 08:00 and 20:00 | "0 8,20 * * *" |
| Monday to Friday at 07:30 | "30 7 * * 1-5" |
| Every Monday at 07:00 | "0 7 * * 1" |
| First Monday of the month at 07:00 | "0 7 * * 1#1" |
| First day of the month at 05:00 | "0 5 1 * *" |
| Last day of the month at 23:00 | "0 23 L * *" |
| 1 January, April, July and October | "@quarterly" |
| Every 3 days | timedelta(days=3) |
| Every 6 hours, counted from the start date | timedelta(hours=6) |
| Only when triggered | None |
| Once | "@once" |
A complete DAG:
import pendulum
from airflow.sdk import dag
@dag(
schedule="*/15 * * * *",
start_date=pendulum.datetime(2026, 1, 1, tz="UTC"),
catchup=False,
)
def every_15_minutes():
...
every_15_minutes()
Two times that a single cron cannot express, such as 08:30 and 17:00:
from airflow.sdk import MultipleCronTriggerTimetable
schedule = MultipleCronTriggerTimetable("30 8 * * *", "0 17 * * *", timezone="Europe/Rome")
Reading a cron expression
A cron expression has five fields: minute, hour, day of the month, month and day of the week.
* means every value, */n every nth value, 1-5 a range and 8,20 a list. Days of the week go
from 0 (Sunday) to 6 (Saturday), and 7 is Sunday again. The presets are shortcuts:
| Preset | Cron | Runs |
|---|---|---|
@hourly |
0 * * * * |
every hour at minute 0 |
@daily |
0 0 * * * |
every day at midnight |
@weekly |
0 0 * * 0 |
Sunday at midnight |
@monthly |
0 0 1 * * |
the 1st at midnight |
@quarterly |
0 0 1 */3 * |
1 January, April, July and October |
@yearly |
0 0 1 1 * |
1 January at midnight |
Traps
"* */2 * * *"runs every minute. The first field is the minute, and*means every minute of every other hour: 60 runs instead of one. Write"0 */2 * * *"."0 0 */3 * *"is not every three days. The day of the month starts again in every month, so the DAG runs on 31 May and again on 1 June. For a real three-day interval usetimedelta(days=3).- Day of the month and day of the week together mean “or”.
"0 0 1 * 1"runs on the 1st of the month and also on every Monday, not only on Mondays that fall on the 1st. - A timedelta counts from the start date, not from the clock. With a start date at 01:15,
timedelta(hours=6)runs at 01:15, 07:15, 13:15 and 19:15. Use cron when the runs must fall on round times. - The time is the start, not the end. In Airflow 3 the run scheduled at 06:00 starts at 06:00 and
its
dsis that day. In Airflow 2 a daily run was dated the day before; see why your DAG processes yesterday’s data.
Checked against the Airflow 3.3.2 scheduler: every expression above was expanded into its next runs.