Skip to main content

Advanced Scheduling with Crontab

To schedule periodic tasks with calendar-based precision, use the crontab schedule in your Celery Beat configuration. This allows you to define execution times using minutes, hours, days of the week, days of the month, and months of the year.

Basic Configuration

You can define a crontab schedule within your app's beat_schedule setting. The following example schedules a cleanup task to run every day at 4:00 AM.

from celery.schedules import crontab
from .celery import app

app.conf.beat_schedule = {
'cleanup-every-morning': {
'task': 'tasks.cleanup',
'schedule': crontab(hour=4, minute=0),
},
}

The crontab class (found in celery/schedules.py) accepts the following keyword arguments:

  • minute: 0-59
  • hour: 0-23
  • day_of_week: 0-6 (Sunday is 0)
  • day_of_month: 1-31
  • month_of_year: 1-12

Using Cron Expressions

The crontab schedule supports advanced expressions similar to standard Unix cron. These are parsed by the internal crontab_parser class.

Ranges and Lists

You can specify ranges using a hyphen or lists using commas.

# Run every weekday (Monday through Friday) at 7:30 AM
crontab(hour=7, minute=30, day_of_week='mon-fri')

# Run at 8:00 AM on the 1st, 15th, and 30th of the month
crontab(hour=8, minute=0, day_of_month='1,15,30')

Steps

Use the forward slash / to specify intervals within a range.

# Run every 15 minutes
crontab(minute='*/15')

# Run every 2 hours during office hours (9 AM to 5 PM)
crontab(hour='9-17/2', minute=0)

Parsing from Strings

If you prefer the standard 5-field cron format, use the from_string class method.

# Equivalent to: 8:00 AM on the 5th of every month
# Fields: minute, hour, day_of_month, month_of_year, day_of_week
schedule = crontab.from_string('0 8 5 * *')

Advanced Day Scheduling (AND Logic)

In this codebase, if you specify both day_of_month and day_of_week, the task will only run on days that satisfy both conditions. This differs from some standard cron implementations that use "OR" logic.

# Run only on the first Monday of every month
# (Day of month 1-7 AND Day of week is Monday)
crontab(hour=7, minute=30, day_of_week='mon', day_of_month='1-7')

Handling Missed Tasks

If the scheduler is offline and misses a scheduled run, it may attempt to run the task immediately upon restart. You can control this behavior using the beat_cron_starting_deadline setting.

# If a task is more than 30 seconds late, do not run it until the next scheduled time
app.conf.beat_cron_starting_deadline = 30

This setting is checked within the crontab.is_due() method to determine if a past feasible run date is still within the allowed window.

Common Pitfalls

Day of Week Steps

The expression day_of_week='*/2' does not mean "every two days." It means "every day where the numeric index is divisible by two." Since Sunday is 0, this triggers on Sunday (0), Tuesday (2), Thursday (4), and Saturday (6).

Invalid Date Combinations

The crontab logic includes protection against infinite loops in its rollover calculations (see _delta_to_next in celery/schedules.py). If you provide a specification that can never be met (e.g., February 31st), the scheduler will eventually raise a RuntimeError after 2000 attempts to find a valid next run time.

Negative Numbers

The crontab_parser does not support negative numbers in expressions. Attempting to use them will result in a ParseException.