Periodic tasks with beat

Celery · Practical · 10 min

Nightly reports, cache warmups and cleanup jobs are periodic tasks, and Celery replaces cron without extra infrastructure.

celery beat is a separate scheduler process. It doesn't execute tasks; it sends them to the broker on schedule, and normal workers run them.

Define schedules in app.conf.beat_schedule, a dict of entries:

  • "task": the registered task name.
  • "schedule": seconds (30.0), a timedelta, or a crontab(...).
  • "args" / "kwargs" and optional "options" (same as apply_async, e.g. a queue).

crontab(minute=0, hour=3) is daily at 03:00; crontab(minute="*/15") every 15 minutes; crontab(hour=7, minute=30, day_of_week="mon-fri") weekdays at 7:30. Times use the app's timezone setting.

Start it with celery -A proj beat -l INFO. In development you can embed it in a worker with celery -A proj worker -B, but not in production. Beat keeps its last-run times in a local celerybeat-schedule file by default; django-celery-beat stores schedules in the database and lets admins edit them.

The crucial rule: run exactly one beat process. Two schedulers send every task twice. In containers, beat gets its own single-replica deployment.

If a job must not overlap with its previous run (long reports), add a lock in the task, e.g. a Redis SET NX with expiry, because beat doesn't check whether the last run finished.

Example

# proj/celery.py
from celery import Celery
from celery.schedules import crontab

app = Celery("proj", broker="redis://localhost:6379/0")
app.conf.timezone = "UTC"
app.conf.beat_schedule = {
    "cleanup-sessions-every-15-min": {
        "task": "proj.tasks.cleanup_sessions",
        "schedule": crontab(minute="*/15"),
    },
    "nightly-report": {
        "task": "proj.tasks.build_report",
        "schedule": crontab(hour=3, minute=0),
        "args": ("daily",),
        "options": {"queue": "reports"},
    },
    "heartbeat": {"task": "proj.tasks.ping", "schedule": 30.0},
}
# $ celery -A proj beat -l INFO        (one instance only)
# $ celery -A proj worker -l INFO -Q celery,reports

Remember

  • beat only sends tasks on schedule; workers execute them
  • Schedules: seconds, timedelta or crontab(...) in the app timezone
  • Run exactly one beat instance in production
  • Use a lock if runs must not overlap

Watch out for

  • Running beat inside every worker replica with -B, so each job runs once per replica.

Check yourself

  1. You scale workers to 4 replicas, each started with -B. What happens to a 'nightly-report' entry?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.celeryq.dev/en/stable/userguide/periodic-tasks.html

Builds on

Unlocks

Nothing builds on this yet.