Scheduled and Periodic Jobs with Celery Beat
Run recurring jobs using Celery Beat and coordinate cron-style schedules across multiple workers safely.
Why Celery Beat?
Your FastAPI app fires off one-off background tasks with Celery just fine, but some work must run on a schedule: send a digest email every morning, expire abandoned carts every 10 minutes, recompute analytics nightly.
Celery Beat is a scheduler process. It does not execute tasks itself — it wakes up on a tick, decides which tasks are due, and pushes them onto the broker (Redis/RabbitMQ). Your normal celery worker processes pick them up and run them.
- Beat = the clock that publishes due tasks.
- Worker = the muscle that runs them.
This separation is the whole reason periodic jobs scale: one Beat, many workers.
Defining the schedule
Schedules live on the Celery app under conf.beat_schedule. Each entry maps a name to a dict with the task path, a schedule (seconds, timedelta, or a crontab), and optional args/kwargs.
The simplest schedule is a fixed interval. Below, the cleanup task runs every 30 seconds. The task path string must match the worker's registered name exactly.
from celery import Celery
from datetime import timedelta
app = Celery("jobs", broker="redis://localhost:6379/0")
@app.task(name="tasks.cleanup_sessions")
def cleanup_sessions():
# delete expired sessions from the DB
return "cleaned"
app.conf.beat_schedule = {
"cleanup-every-30s": {
"task": "tasks.cleanup_sessions",
"schedule": timedelta(seconds=30),
},
}All lessons in this course
- Lightweight Offloading with BackgroundTasks
- Wiring Celery Workers to a FastAPI App
- Retries, Idempotency and Dead-Letter Handling
- Scheduled and Periodic Jobs with Celery Beat