Celery Beat Sends Tasks, but Your Worker Does Not Run Them
Your periodic task appears in Beat's log. A worker answers a ping. The report you expected still does not exist. Those observations can all be true at once: the worker may not consume the queue receiving that task.
We reproduced that configuration with Celery 5.7.0 and a disposable Redis broker. In each of two fault runs, the specific Beat process and worker both answered their probes while two published task IDs remained queued. Attaching that same worker to the intended queue let those original IDs finish. No replacement tasks were submitted to make the repair look successful.
That is one possible diagnosis, not a conclusion you can draw from a missing report alone. This guide starts with read-only checks, then supplies the source and evidence for the isolated reproduction. It is useful whether you monitor completion with PingCron, another service or your own system.
Find the last stage with evidence
Treat a scheduled task as a sequence: Beat decides a task is due, the producer publishes a message, a worker consumes it, and the task produces its required output. A signal from one stage cannot establish all the later stages.
| Observation | What to investigate next | What it does not establish |
|---|---|---|
| No expected due occurrence | Deployed schedule, timezone and scheduler state | That a worker is broken |
| Beat logs a due task | Publication errors and the actual destination | That the broker accepted a message |
| Message exists in the intended queue | Worker subscriptions, capacity and task registration | That a worker has started it |
| Worker started the task ID | Exceptions, time limits and required output | That the work completed |
| Required output was validated | Completion reporting and the next expected occurrence | That every future run will succeed |
Record the occurrence time, deployed application version, task name and task ID when available. Keep observations from the same occurrence together. Yesterday's successful task and today's healthy worker process do not explain today's missing output.
Avoid beginning with a purge, deletion of the schedule database, or an indiscriminate restart. Those actions change evidence and may discard work. First identify which boundary the task crossed. If the job sends messages or performs other side effects, replaying it can also create duplicates.
1. Check the deployed application and task name
Compare the app selected by Beat with the app selected by the intended worker. A local development shell can import different settings from the service launched by your process manager or container. Check the deployed command, working directory and configuration source rather than assuming that a successful local import proves the deployed setup.
Privately compare broker destinations, including Redis database or RabbitMQ virtual host where applicable. Do not paste credentials or full configuration dumps into tickets, logs or public discussions. Different services can each look healthy while using different brokers.
Check the task identifier used by the schedule against the worker's registered names. An importable Python function, a registration name and the display name of a periodic schedule entry are different things. The worker must know the task identifier carried by the message. Celery's task naming documentation explains explicit and automatically generated names.
Use the actual app and worker node name in these read-only examples:
celery -A your_app inspect --destination=worker@your-host ping
celery -A your_app inspect --destination=worker@your-host registered
celery -A your_app inspect --destination=worker@your-host active_queues
These commands require the same authorized broker access as your operational tooling. They send inspection requests; they do not execute the application task. A missing reply can also reflect unavailable remote control, a busy node, broker connectivity or an insufficient response timeout. It does not uniquely identify a dead process. See Celery's inspection commands and destination selection.
2. Compare the routed queue with the worker's subscriptions
Determine the effective destination for this specific task. Review the schedule entry's options, task routing configuration and the worker's launch options. Do not stop at finding a queue name somewhere in the repository: configuration precedence and deployment overrides matter.
In our fixture, the scheduled task is routed to a uniquely named queue ending in _reports. The working baseline starts the worker with that queue. The fault changes only the worker's initial subscription to a queue ending in _other.
Scheduled task destination: pc_celery_lab_<unique-id>_reports
Baseline worker -Q: pc_celery_lab_<unique-id>_reports
Fault worker -Q: pc_celery_lab_<unique-id>_other
The worker still registers pingcron_lab.report in both cases. Registration answers whether it knows the task; subscription answers whether it consumes the queue holding the message. Neither fact substitutes for the other.
Compare active_queues with the effective task destination. If they differ, establish whether another intended worker consumes that queue before declaring the whole system stalled. A dedicated reporting worker may be correct architecture. Conversely, a general worker consuming its default queue does not automatically cover every explicitly routed queue. Celery documents the relationship between task routes and worker queue selection.
If you confirm a deployment mistake, correct the intended worker's persistent configuration through your normal rollout process. A temporary consumer added at runtime is not a substitute for fixing configuration that will return on the next restart. Consider capacity and task side effects before releasing accumulated work.
3. Check Beat separately from the worker
Celery 5.7 supports optional remote control for Beat. Its health response considers scheduler progress, so it is more informative than simply finding a process ID. The lab enables beat_enable_remote_control and sets beat_remote_control_max_tick_age to 10 seconds. It targets the explicit Beat node name, separately from the worker.
A successful Beat response still does not prove a particular task was consumed or completed. The probe depends on broker communication, too. Treat missing responses as a reason to investigate, not an automatic restart verdict. Older Celery versions and deployments without the option enabled need their own checks. See the current Beat health-check semantics.
Another common source of confusion is inspect scheduled. That command reports ETA/countdown work reserved by workers. It is not an inventory of Beat's periodic timetable. An empty result therefore does not prove that your Beat schedule is missing. Inspect the schedule used by the deployed scheduler itself.
4. Correlate publication with execution
Beat's Sending due task message is written before the publication attempt in Scheduler.apply_entry. Read the subsequent error context rather than treating that single line as a broker receipt. This ordering is visible in Celery's scheduler implementation.
Our fixture records task IDs through after_task_publish, then checks the actual Redis queue contents in the mismatch phase. The producer-side event alone is not presented as an end-to-end guarantee. The additional queue snapshot establishes that those IDs were pending in this controlled configuration. The Celery signal reference describes the publication hook.
For a real incident, use broker tooling appropriate to the transport and configuration. Our Redis list inspection assumes this fixture's nonpriority queue layout; it is not a universal command for RabbitMQ, priority queues or all Redis transport settings. A zero ready-message count can also mean messages were reserved or moved, rather than completed.
Follow the same task ID into worker evidence and the required output. Decide what completion means for your application: a committed record, a validated export or a finished upload. A function returning without raising can still leave the wrong business result. Keep that application-level check separate from the transport diagnosis. For backup jobs, the PostgreSQL backup verification walkthrough shows why producing a file and testing a restore are separate checks.
Reproduce the queue mismatch safely
The downloadable fixture uses only a dedicated disposable Redis instance on 127.0.0.1, unique queue and node names, and a synthetic summation task. It refuses inherited CELERY_* settings so an external broker override cannot silently redirect it. It requires explicit acknowledgement and stops the worker and Beat processes it owns. It never calls PingCron or sends an external heartbeat.
Do not point it at production, including a production broker forwarded to localhost. It deliberately changes a test worker subscription and executes the queued fixture tasks.
- Download the source and full evidence bundle.
- Read the machine-readable result.
- Inspect the tested Python dependencies.
- Read the setup and evidence inventory.
The run used Python 3.12.16, Celery 5.7.0, redis-py 8.1.0 and Redis 7.4.12 on a Linux GitHub Actions runner. The full report records source hashes, exact process commands, timestamps and task IDs. The bundle includes both repeats' worker and Beat logs. These are synthetic results recorded on November 13, 2026, not a customer incident or a performance benchmark.
| Phase | Targeted Beat / worker response | Initial worker queue | Pending before repair | Validated completions |
|---|---|---|---|---|
| Baseline 1 | Both pong | Intended reports queue | Not a fault snapshot | 2 of 2 published IDs |
| Mismatch 1 | Both pong | Other queue | 2 original IDs | 0 before repair, 2 afterward |
| Baseline 2 | Both pong | Intended reports queue | Not a fault snapshot | 2 of 2 published IDs |
| Mismatch 2 | Both pong | Other queue | 2 original IDs | 0 before repair, 2 afterward |
The producer stops before the mismatch snapshot and repair. The harness then adds the intended queue to that exact test worker. Each previously pending ID must appear in both worker-start and completion records. Each completion must contain the validated sum, 50, from three fixture inputs. Finally, the intended queue must be empty and present in the worker's reported subscriptions.
That sequence rules out a misleading repair where a newly submitted replacement task succeeds while the original messages remain stuck. It does not establish exactly-once processing, durable recovery across crashes, retry behavior, or arbitrary application correctness. Two repeats demonstrate this configuration; they are not a statistical reliability estimate.
Run the fixture in a disposable Linux environment
Extract the bundle into a fresh directory. Use a clean Python environment, and start a dedicated Redis instance bound only to loopback. The README supplies a container example and cleanup commands scoped to that container. Do not reuse a shared broker.
python3 -m venv .venv
. .venv/bin/activate
python -m pip install -r evidence/installed-requirements.txt
export LAB_REDIS_URL=redis://127.0.0.1:16379/0
python source/test_safety.py
python source/run.py --ack-disposable-broker
The harness writes a new celery-routing-evidence directory and refuses to overwrite an existing one. Each run has two baseline phases and two mismatch-and-repair phases. Installation still depends on package availability and your environment; the recorded versions describe the observed run, not a promise of byte-identical future execution.
Detect the next missing completion
Once routing is correct, decide how you will notice a future missed occurrence. A worker process can remain reachable while a specific periodic job stops producing output. The lab demonstrates why process probes and task completion answer different questions.
For PingCron, send the success heartbeat from inside the worker task after the required work and validation finish. Do not send it immediately after dispatching the task, and do not put success reporting in a finally block that also runs on failure. Preserve the application's existing exception and retry behavior.
The Python completion-heartbeat guide covers setup and verification. Match the expected schedule and allow appropriate runtime and delay. A receipt establishes that a signal arrived; it does not independently validate the business output. PingCron can alert when an expected heartbeat is missing, but it does not fix queue routing, execute the task or replace Flower's task-level inspection.
You can create a PingCron account if that missing-completion signal fits your setup. Verify the first automatic run and your alert destination before relying on it. Keep the task ID and output checks in your operational records so an alert leads to evidence rather than a guess.
Related monitoring guides
For adjacent problems, use the cron monitoring walkthrough when cron launches the Python process directly, or Docker cron troubleshooting when the missing boundary is the scheduler inside a container. Those are different execution paths from Beat publishing to a broker. If you are choosing where the independent completion check should run, compare the operational responsibilities in self-hosted versus hosted cron monitoring.