Celery Beat Schedule Guide
Celery Beat is the scheduler component for Celery, Python's most widely used distributed task queue — it periodically kicks off tasks according to a schedule defined in code rather than a separate crontab file, using either simple interval schedules or a crontab()-style helper that mirrors standard cron fields.
Syntax Notes
Celery's crontab() helper takes keyword arguments (minute=, hour=, day_of_week=, day_of_month=, month_of_year=) that each accept the same wildcards, ranges, steps, and lists as standard cron fields — just expressed as Python keyword arguments instead of a single 5-field string.
Examples
from celery.schedules import crontab
app.conf.beat_schedule = {
'nightly-cleanup': {
'task': 'myapp.tasks.cleanup',
'schedule': crontab(hour=2, minute=30),
},
}'schedule': crontab(minute='*/15')'schedule': crontab(hour=9, minute=0, day_of_week='1-5')Gotchas
- Celery Beat must run as its own separate process (celery -A myapp beat) alongside the worker process — a common mistake is running only celery worker and wondering why scheduled tasks never fire, since the worker executes tasks but never schedules them.
- Running more than one Beat instance simultaneously schedules every task multiple times — Beat is a singleton scheduler by design, so use a single Beat process (or a distributed lock via django-celery-beat's database scheduler) rather than one Beat per worker replica.
- crontab(minute='*/15') has the exact same step-value gotcha as plain cron — it only fires at :00, :15, :30, :45, not truly every 15 minutes from whenever the app started.
Best Practices
- Deploy exactly one Beat process per environment, separate from your worker processes — scaling out worker replicas should never mean scaling out Beat.
- For dynamic, database-editable schedules (rather than ones baked into code that require a redeploy to change), use the django-celery-beat package's DatabaseScheduler instead of the default file-based one.
- Double-check timezone handling — Celery's timezone defaults to UTC unless CELERY_TIMEZONE (or app.conf.timezone) is explicitly set, a common source of schedules firing at unexpected local times.
Frequently Asked Questions
- Why isn't my Celery Beat schedule running at all?
- The most common cause is not running a Beat process — celery worker alone executes tasks but never schedules them. You need a separate `celery -A myapp beat` process (or `celery -A myapp worker -B` to run both in one process, fine for development but not recommended in production).
- Can Celery Beat schedules be edited without redeploying code?
- Not with the default file-based scheduler, since beat_schedule is defined in application code. Use the django-celery-beat package, which stores schedules in the database and lets you edit them via Django admin or the ORM without a redeploy.
- Does crontab(minute='*/15') in Celery behave differently from cron's */15?
- No — it has the exact same semantics as cron: it fires at fixed clock positions (:00, :15, :30, :45), not on a rolling 15-minute timer from whenever the app started or was last restarted.
Related Tools
Platform Guides
Cron syntax for Linux, Kubernetes, AWS, and more
Rails Cron Jobs with Whenever
Schedule background jobs in Ruby on Rails with the whenever gem: schedule.rb syntax, writing a crontab automatically, and common deployment gotchas.
Vercel Cron Jobs Guide
Schedule serverless functions on Vercel with Cron Jobs: vercel.json syntax, plan-based frequency limits, authentication, and common gotchas.