Skip to content
Get started

How to monitor Celery beat periodic tasks

Celery beat is a single scheduler process that dispatches your periodic tasks, so if the beat process dies, every periodic task stops without an error. To monitor it, send a heartbeat to an external monitor from inside a task (or from the task_success signal) and alert when the ping doesn't arrive in the expected window. Watch specifically for beat running but a worker being down, which drops tasks silently.

Why did my Celery periodic task stop running?

Celery splits scheduling from execution. beat decides when a task should run and puts it on the broker; a worker picks it up and executes it. A periodic task silently stops when any link breaks:

  • The beat process crashed or was never restarted after a deploy, so nothing is scheduling tasks.
  • beat is running but every worker is down, so tasks are queued on the broker and never executed.
  • Two beat processes are running (for example after a bad rolling deploy), causing duplicate or skipped schedules.
  • The broker (Redis or RabbitMQ) is unreachable, so scheduled messages never land.

How do I send a heartbeat from a Celery task?

Have the periodic task itself report success to a monitor as its last action. A missed ping then means either beat didn't schedule it or no worker ran it — both of which you want to know about.

tasks.py
import httpx
from celery import shared_task

PING_URL = "https://ping.cronshield.com/<your-check-id>"

@shared_task
def refresh_reports():
    do_the_work()
    # Report success as the final action. If the task never runs or raises
    # before this line, no ping arrives and the monitor alerts on the miss.
    httpx.get(PING_URL, timeout=10)

To avoid editing every task, hook the task_success signal and ping only for the periodic tasks you monitor:

signals.py
import httpx
from celery.signals import task_success

MONITORED = {"tasks.refresh_reports": "https://ping.cronshield.com/<your-check-id>"}

@task_success.connect
def on_task_success(sender=None, **kwargs):
    url = MONITORED.get(sender.name if sender else "")
    if url:
        httpx.get(url, timeout=10)
PING_URL is a placeholder for the check-specific endpoint you'll get when you create a monitor. The CronShield receiver ships in an upcoming release.

How do I know if it's beat or the worker that failed?

A missed ping tells you the task didn't complete, but not which link broke. On the free tier you get the miss alert and check beat and worker health yourself. On paid tiers, CronShield ingests the task's stdout/stderr, so an exception raised inside the task arrives with the last log line and a likely cause — which usually distinguishes a task that errored from a scheduler or worker that never ran it.

Add a missed-run alert to this job

The free tier gives you a heartbeat endpoint and an email alert when an expected ping doesn't arrive. Paid tiers add the log-aware diagnosis — the last log line and a likely cause in the alert. The heartbeat receiver ships in an upcoming release; see the plans to learn what each tier adds.

Frequently asked questions

Can Celery tell me a periodic task was skipped?
Not on its own. Celery logs tasks it runs, but a task that beat never scheduled (because beat was down) produces no log at all. An external heartbeat is what surfaces the skipped run.
Should I ping from inside the task or from a signal?
Either works. Pinging inside the task keeps the logic explicit; the task_success signal keeps your task bodies clean and centralizes which tasks are monitored. Both report success only when the task actually completed.