Celery Beat Python Cron Guide
Celery Beat is the periodic task scheduler for the Celery task queue — the most popular background job system in Python. It lets you schedule tasks using cron-style crontab() expressions, fixed timedelta intervals, or solar schedules — all without a separate cron daemon.
Contents
What Is Celery Beat?
Celery is a distributed task queue for Python. Celery Beat is the built-in scheduler that kicks off tasks on a time-based schedule, similar to a Linux crontab — but tasks execute inside Celery workers, giving you full access to your application context, database connections, and retry logic.
Architecture
Beat Scheduler ──► Message Broker (Redis/RabbitMQ) ──► Celery Workers
│ │
│ (enqueues tasks at scheduled times) (executes tasks) │
└─────────────────────────────────────────────────────────┘Beat and workers are separate processes. In production you run one Beat instance and one or more workers. Beat only enqueues — workers do the actual work.
Installation
shell
# Core Celery (Beat is included) pip install celery # With Redis as broker pip install celery[redis] # For Django projects (includes database scheduler) pip install django-celery-beat
beat_schedule Configuration
Define periodic tasks in your Celery app config using the beat_schedule dictionary. Each key is the schedule name; each value is a dict with task, schedule, and optionally args/kwargs.
celery.py
from celery import Celery
from celery.schedules import crontab
app = Celery('myproject', broker='redis://localhost:6379/0')
app.conf.beat_schedule = {
# Run every Monday morning at 7:30 AM
'weekly-report': {
'task': 'myapp.tasks.send_weekly_report',
'schedule': crontab(minute=30, hour=7, day_of_week='monday'),
'args': ('admin@example.com',),
},
# Run every 5 minutes
'sync-inventory': {
'task': 'myapp.tasks.sync_inventory',
'schedule': crontab(minute='*/5'),
'kwargs': {'force': False},
},
# Run daily at midnight using timedelta
'daily-cleanup': {
'task': 'myapp.tasks.purge_old_logs',
'schedule': timedelta(days=1),
},
}
app.conf.timezone = 'UTC'Always set app.conf.timezone explicitly. It defaults to UTC, which is recommended for production.
crontab() Syntax Reference
crontab() accepts keyword arguments matching standard cron fields:
| Argument | Type | Default | Equivalent cron field |
|---|---|---|---|
| minute | int / str | '*' | minute (0–59) |
| hour | int / str | '*' | hour (0–23) |
| day_of_week | int / str | '*' | day-of-week (0=SUN, 1=MON…) |
| day_of_month | int / str | '*' | day-of-month (1–31) |
| month_of_year | int / str | '*' | month (1–12) |
Strings support the same special characters as standard cron:
*Any value (all)*/NEvery N units — e.g. minute='*/15'N-MRange — e.g. hour='9-17'N,MList — e.g. day_of_week='1,3,5'mon-friNamed weekday range0Sunday (when day_of_week=0)crontab() vs Standard Cron
| Description | Celery crontab() | Standard cron |
|---|---|---|
| Every minute | crontab() | * * * * * |
| Every 30 minutes | crontab(minute='*/30') | */30 * * * * |
| Every hour at :00 | crontab(minute=0) | 0 * * * * |
| Daily at midnight | crontab(minute=0, hour=0) | 0 0 * * * |
| Daily at 9 AM | crontab(minute=0, hour=9) | 0 9 * * * |
| Every weekday at 8 AM | crontab(minute=0, hour=8, day_of_week='mon-fri') | 0 8 * * 1-5 |
| Every Sunday at midnight | crontab(minute=0, hour=0, day_of_week=0) | 0 0 * * 0 |
| First of month at 1 AM | crontab(minute=0, hour=1, day_of_month=1) | 0 1 1 * * |
timedelta Intervals
For simple fixed intervals, use Python's timedelta instead of crontab(). It's simpler for "every N seconds/minutes/hours" patterns, but doesn't anchor to clock boundaries.
celery.py
from datetime import timedelta
app.conf.beat_schedule = {
# Every 30 seconds
'heartbeat': {
'task': 'myapp.tasks.ping',
'schedule': timedelta(seconds=30),
},
# Every 5 minutes
'cache-warm': {
'task': 'myapp.tasks.warm_cache',
'schedule': timedelta(minutes=5),
},
# Every 6 hours
'data-snapshot': {
'task': 'myapp.tasks.snapshot_data',
'schedule': timedelta(hours=6),
},
}timedelta vs crontab()
timedelta(hours=6)— runs every 6h from when Beat startedcrontab(minute=0, hour='*/6')— runs at 00:00, 06:00, 12:00, 18:00 UTC- Use
crontab()when you need clock-aligned scheduling
Django Celery Beat Integration
For Django projects, the standard setup is django-celery-beatwhich stores schedules in Django's database, enabling dynamic schedule management via the Django admin.
settings.py
INSTALLED_APPS = [
...
'django_celery_beat',
]
# Use database scheduler
CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler'celery.py (Django)
import os
from celery import Celery
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
app = Celery('myproject')
app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks() # auto-find tasks.py in each INSTALLED_APPshell
# Run migrations to create the schedule tables python manage.py migrate django_celery_beat
After migrating, periodic tasks appear in Django Admin under Periodic Tasks. You can add, edit, and disable schedules without redeploying code.
Database-Backed Schedules
Use PeriodicTask and CrontabSchedule models to create schedules programmatically:
Python (Django ORM)
from django_celery_beat.models import PeriodicTask, CrontabSchedule
import json
# Create or get a cron schedule: every weekday at 9 AM UTC
schedule, _ = CrontabSchedule.objects.get_or_create(
minute='0',
hour='9',
day_of_week='mon-fri',
day_of_month='*',
month_of_year='*',
timezone='UTC',
)
# Create the periodic task
PeriodicTask.objects.get_or_create(
name='Morning Report',
task='myapp.tasks.send_morning_report',
crontab=schedule,
defaults={
'kwargs': json.dumps({'recipient': 'team@example.com'}),
'enabled': True,
}
)Running the Beat Scheduler
shell
# Start worker (in one terminal) celery -A myproject worker -l info # Start beat scheduler (in another terminal) celery -A myproject beat -l info # Combined (development only — NOT recommended for production) celery -A myproject worker --beat -l info # With django-celery-beat database scheduler celery -A myproject beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler
Warning: Run Only One Beat Instance
Run exactly one Beat process in your cluster. Multiple Beat instances will enqueue duplicate tasks. Beat is not designed to run in a cluster — use the database scheduler and a distributed lock if you need HA.
Production Best Practices
1. Always run Beat as a separate process
Never use --beat with the worker in production. Beat needs to be a single, stable process. Use systemd, Supervisor, or a process manager to keep it running.
2. Use UTC for all schedules
Set CELERY_TIMEZONE = 'UTC' and use UTC in all crontab() calls. Store user-facing times in UTC and convert in the application layer.
3. Make tasks idempotent
Tasks can run more than once (network retries, worker restarts). Design tasks to produce the same result if run twice — check a flag before acting, or use database constraints.
4. Set expires on time-sensitive tasks
Add expires= to prevent stale tasks from executing after a long queue backup: beat_schedule entry can include 'options': {'expires': 3600}.
5. Monitor with Flower or a metrics exporter
Run Flower (pip install flower) or export Celery metrics to Prometheus. Alert on failed tasks and queue depth.
6. Persist the beat schedule file
Beat writes state to celerybeat-schedule (or DB). In containers, mount this as a persistent volume or use the database scheduler so schedule state survives restarts.
Common Pattern Reference
celery.py — complete beat_schedule example
from celery.schedules import crontab
from datetime import timedelta
app.conf.beat_schedule = {
# Heartbeat every 30 seconds
'heartbeat': {
'task': 'myapp.tasks.ping_health',
'schedule': timedelta(seconds=30),
},
# Cache warm every 5 minutes
'warm-cache': {
'task': 'myapp.tasks.warm_cache',
'schedule': crontab(minute='*/5'),
},
# Business hours email digest (Mon–Fri 8 AM)
'morning-digest': {
'task': 'myapp.tasks.send_digest',
'schedule': crontab(minute=0, hour=8, day_of_week='mon-fri'),
},
# Nightly DB cleanup at 2 AM
'db-cleanup': {
'task': 'myapp.tasks.cleanup_expired_sessions',
'schedule': crontab(minute=0, hour=2),
},
# Monthly invoice generation — 1st of month at 6 AM
'monthly-invoices': {
'task': 'myapp.tasks.generate_invoices',
'schedule': crontab(minute=0, hour=6, day_of_month=1),
},
# Weekly report — every Sunday at midnight
'weekly-report': {
'task': 'myapp.tasks.send_weekly_report',
'schedule': crontab(minute=0, hour=0, day_of_week='sunday'),
},
}Convert crontab() to plain English
Paste any cron expression — including Celery's crontab() args — into our debugger to see the next run times and a plain-English explanation.
Open Cron Debugger