> ## Documentation Index
> Fetch the complete documentation index at: https://docs.devhelm.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Cron Job Monitoring

> Monitor cron jobs and scheduled tasks with DevHelm heartbeat monitors

By the end of this guide, you'll have heartbeat monitors tracking your scheduled jobs, with alerts firing when a job stops running on schedule.

<Accordion title="Prerequisites">
  * DevHelm CLI installed or an API token
  * One or more cron jobs or scheduled tasks to monitor
</Accordion>

## The heartbeat pattern

Standard monitors work by checking your service outbound. For cron jobs, the pattern is reversed — **your job pings DevHelm** after each successful run. If DevHelm doesn't receive a ping within the expected window, it opens an incident.

## Set up a heartbeat monitor

<Steps>
  <Step title="Create the monitor">
    <CodeGroup>
      ```bash CLI theme={null}
      devhelm monitors create \
        --name "Nightly Backup" \
        --type HEARTBEAT
      ```

      ```yaml devhelm.yml theme={null}
      monitors:
        - name: Nightly Backup
          type: HEARTBEAT
          config:
            expectedInterval: 86400
            gracePeriod: 3600
      ```
    </CodeGroup>

    The CLI creates heartbeat monitors with a 60-second expected interval and grace period. For a daily job like this one, set `expectedInterval` and `gracePeriod` via YAML config-as-code (shown above) or the API.

    | Config field       | Value   | Meaning                               |
    | ------------------ | ------- | ------------------------------------- |
    | `expectedInterval` | `86400` | Job runs every 24 hours               |
    | `gracePeriod`      | `3600`  | Allow 1 hour of slack before alerting |
  </Step>

  <Step title="Get the ping URL">
    ```bash theme={null}
    devhelm monitors get <monitor-id>
    ```

    Copy the `pingUrl` from the response.
  </Step>

  <Step title="Add the ping to your job">
    Add a curl at the end of your script:

    ```bash theme={null}
    #!/bin/bash
    set -e

    # Your job logic
    pg_dump mydb > /backups/nightly-$(date +%Y%m%d).sql
    aws s3 cp /backups/nightly-$(date +%Y%m%d).sql s3://backups/

    # Ping DevHelm on success
    curl -fsS -o /dev/null https://api.devhelm.io/api/v1/heartbeat/<token>
    ```

    Using `curl -fsS` ensures the ping is only sent on HTTP success. If the script fails (`set -e`), the curl never runs and DevHelm eventually alerts.
  </Step>
</Steps>

## Common intervals

| Job          | expectedInterval | gracePeriod     |
| ------------ | ---------------- | --------------- |
| Every minute | `60`             | `30`            |
| Hourly       | `3600`           | `300` (5 min)   |
| Daily        | `86400`          | `3600` (1 hour) |
| Weekly       | `604800`         | `86400` (1 day) |

## Advanced patterns

<AccordionGroup>
  <Accordion title="Kubernetes CronJobs">
    Add a post-completion container or use a `completions`-based Job with a sidecar:

    ```yaml theme={null}
    apiVersion: batch/v1
    kind: CronJob
    metadata:
      name: nightly-backup
    spec:
      schedule: "0 2 * * *"
      jobTemplate:
        spec:
          template:
            spec:
              containers:
                - name: backup
                  image: myapp/backup:latest
                  command: ["/bin/sh", "-c"]
                  args:
                    - |
                      /scripts/backup.sh && \
                      curl -fsS -o /dev/null https://api.devhelm.io/api/v1/heartbeat/$HEARTBEAT_TOKEN
                  env:
                    - name: HEARTBEAT_TOKEN
                      valueFrom:
                        secretKeyRef:
                          name: devhelm-heartbeats
                          key: nightly-backup
              restartPolicy: OnFailure
    ```
  </Accordion>

  <Accordion title="Long-running workers">
    For workers that process queues continuously, add a periodic heartbeat in the main loop:

    ```python theme={null}
    import requests
    import time

    HEARTBEAT_URL = "https://api.devhelm.io/api/v1/heartbeat/<token>"
    HEARTBEAT_INTERVAL = 300  # 5 minutes

    last_heartbeat = 0

    while True:
        message = queue.receive()
        process(message)

        if time.time() - last_heartbeat > HEARTBEAT_INTERVAL:
            requests.post(HEARTBEAT_URL, timeout=5)
            last_heartbeat = time.time()
    ```
  </Accordion>

  <Accordion title="Multiple jobs, one config">
    Define all heartbeat monitors in a single YAML file:

    ```yaml theme={null}
    version: "1"
    monitors:
      - name: Nightly Backup
        type: HEARTBEAT
        config:
          expectedInterval: 86400
          gracePeriod: 3600
        tags: [infrastructure]

      - name: Hourly Report Generation
        type: HEARTBEAT
        config:
          expectedInterval: 3600
          gracePeriod: 300
        tags: [reporting]

      - name: Daily Cleanup
        type: HEARTBEAT
        config:
          expectedInterval: 86400
          gracePeriod: 21600
        tags: [maintenance]
    ```
  </Accordion>

  <Accordion title="Ping only on success">
    Always use `set -e` or equivalent error handling so the ping is skipped when the job fails. This way DevHelm catches both "job didn't run" and "job ran but failed" scenarios.
  </Accordion>
</AccordionGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="Heartbeat reference" icon="heart-pulse" href="/monitoring/heartbeat/overview">
    Full heartbeat configuration and assertions.
  </Card>

  <Card title="First alert" icon="bell" href="/guides/first-alert">
    Get notified when a heartbeat goes silent.
  </Card>

  <Card title="Monitoring as Code" icon="file-code" href="/guides/monitoring-as-code">
    Manage all heartbeat monitors in YAML.
  </Card>
</CardGroup>
