To configure a Django Celery Beat cron schedule using an environment variable, you must parse the string value from your settings into a
celery.schedules.crontab object within your
CELERY_BEAT_SCHEDULE dictionary. This approach allows you to change execution timing without modifying code or restarting the worker, provided the environment is loaded correctly before the beat scheduler initializes.
Understanding Celery Beat and Crontab Objects
Celery Beat is the periodic task scheduler for Celery. It reads a configuration dictionary called
CELERY_BEAT_SCHEDULE to determine which tasks to run and when. The scheduling logic relies on objects from the
celery.schedules module, specifically
crontab,
schedule, and
delta.
The
crontab object is designed to mimic standard Unix cron syntax but uses keyword arguments instead of a single string. For example,
hour=12, minute=0 runs a task daily at noon. When you store your schedule in an environment variable as a string (e.g., "0 12 * * *"), you cannot pass that string directly to the
crontab constructor. You must convert it.
This conversion step is critical because Django loads its settings early in the application lifecycle. If the environment variable is not available or malformed, the Beat scheduler will fail to start, preventing all scheduled tasks from executing. Therefore, robust parsing and error handling are essential components of this configuration.
Setting Up the Environment Variable
First, ensure your deployment environment exports the cron schedule string. In a Linux/macOS terminal or Dockerfile, this looks like:
export CELERY_BEAT_CRON_SCHEDULE="0 12 * * *"
In Windows Command Prompt, use:
set CELERY_BEAT_CRON_SCHEDULE=0 12 * * *
For Docker Compose, add it to your
.env file or the
environment section of your service definition. The format follows the standard five-field cron syntax:
- Minute (0-59)
- Hour (0-23)
- Day of month (1-31)
- Month (1-12)
- Day of week (0-6, Monday is 0)
You can also use special characters like
* (any),
, (list), and
- (range). For instance,
*/5 * * * * runs every five minutes. Ensure there are no spaces around the commas or hyphens, as these break the parser.
Implementing the Schedule Parser in Settings
Open your Django settings file, typically
settings.py. You need to import the necessary modules and define a helper function to parse the environment variable.
import os
from celery.schedules import crontab
def get_cron_schedule():
cron_str = os.environ.get('CELERY_BEAT_CRON_SCHEDULE', '0 0 * * *')
try:
parts = cron_str.split()
if len(parts) != 5:
raise ValueError("Cron string must have exactly 5 fields")
return crontab(
minute=parts[0],
hour=parts[1],
day_of_month=parts[2],
month_of_year=parts[3],
day_of_week=parts[4]
)
except Exception as e:
print(f"Invalid CRON schedule: {cron_str}. Error: {e}")
# Fallback to a default safe schedule
return crontab(minute=0, hour=0)
This function retrieves the variable, splits it by whitespace, and maps each part to the corresponding argument of the
crontab constructor. It includes a fallback to midnight daily if the parsing fails, ensuring your scheduler does not crash entirely due to a typo in the environment variable.
Next, define your
CELERY_BEAT_SCHEDULE dictionary using this parsed object.
CELERY_BEAT_SCHEDULE = {
'my-periodic-task': {
'task': 'myapp.tasks.my_task',
'schedule': get_cron_schedule(),
},
}
By calling
get_cron_schedule() at module load time, the setting is evaluated once when Django starts. This ensures the
crontab object is instantiated correctly before Beat begins its loop.
Verifying the Configuration
After updating your settings, restart your Celery Beat process. Use the following command to start the scheduler alongside your workers:
celery -A myproject beat --loglevel=info
Check the logs for confirmation. You should see output indicating the schedule has been loaded. If you encounter errors, verify that the environment variable is exported in the same shell session where you run the command. In production environments managed by systemd or supervisor, ensure the daemon configuration includes the environment variable in its
Environment directive.
If you need to adjust other scheduling aspects, such as running tasks on specific weekdays, refer to resources on complex scheduling patterns. For example, if you need to schedule meetings on the third Wednesday of every month, understanding broader scheduling concepts can help, though Celery's native crontab does not support "third Wednesday" directly without custom logic. See
Scheduling Way Add Meeting Occurring Certain Time Every Third Wednesday with Google Calendar for context on complex calendar rules, even if the implementation differs.
Always test your cron strings using online validators or local scripts before deploying them to production. A single misplaced character can lead to unexpected task executions or silence.
Common Pitfalls
One common issue is forgetting that Celery uses UTC by default. Your cron schedule is interpreted relative to the server's timezone unless you explicitly set
CELERY_TIMEZONE in your settings. If your server is in UTC but your business hours are EST, you must adjust the cron string accordingly or change the timezone setting.
Another pitfall is caching. Django does not cache settings during runtime, so changing the environment variable requires a full restart of the Django/Celery processes. Hot-reloading does not apply to
CELERY_BEAT_SCHEDULE.
Finally, ensure that the task name specified in
CELERY_BEAT_SCHEDULE matches exactly with the
@shared_task or
@app.task decorator name in your actual task file. Mismatches result in "Unknown task" errors in the Beat logs.
Frequently Asked Questions
Can I use dynamic schedules that change every minute?
No, Celery Beat evaluates the
CELERY_BEAT_SCHEDULE dictionary only at startup. To change schedules dynamically, you must update the database via the admin interface or use the
celery beat management command with a remote config source, but static env vars require a restart.
What happens if the environment variable is missing?
If the variable is missing, the parser falls back to the default value defined in the
os.environ.get call. In our example, this defaults to midnight (
0 0 * * *). Always provide a sensible default to prevent scheduler failure.
Does Celery Beat respect leap seconds?
Standard Unix cron schedulers, including Celery's
crontab, do not handle leap seconds. They operate on a simple minute-by-minute basis. If precise second-level accuracy is required, consider using delta schedules instead.
How do I debug why my task isn't running?
Enable debug logging by passing
--loglevel=debug to the Celery Beat command. Check the logs for "Scheduler: sending due task" messages. If you see "Unknown task," verify the task name matches your Python module path exactly.
Can I combine multiple cron schedules in one project?
Yes, you can define multiple entries in the
CELERY_BEAT_SCHEDULE dictionary. Each entry can use a different parsed cron object or a fixed schedule. Just ensure each key is unique and points to a valid task.