← trebben.dk

How to monitor cron jobs (the practical guide)

March 2026

The pattern is simple: after your job runs, send a ping to a monitoring service. If the ping doesn't arrive on time, you get an alert. That's it.

This guide shows you how to do that for every common scheduler — crontab, systemd timers, Docker, Kubernetes CronJobs, and CI/CD pipelines. The examples use CronPulse, but the pattern works with any heartbeat monitoring tool.

The core idea

Every heartbeat monitor works the same way:

  1. You create a monitor and tell it "my job runs every X hours"
  2. You get a unique ping URL
  3. You add curl to the end of your job
  4. If the ping stops arriving, you get alerted

The ping URL looks something like this:

https://ping.trebben.dk/a1b2c3d4e5f6

Crontab

The most common case. You have a job in crontab -e and you want to know if it stops running.

Basic: ping after success

# Backup runs at 2am, ping on success
0 2 * * * /usr/local/bin/backup.sh && curl -fsS https://ping.trebben.dk/YOUR_SLUG

The && means the ping only fires if the backup exits 0. If the script fails, no ping — and you get alerted.

Better: ping regardless, report exit code

# Run job, capture exit code, always ping
0 2 * * * /usr/local/bin/backup.sh; curl -fsS https://ping.trebben.dk/YOUR_SLUG/$?

Using ; instead of && means the ping fires either way. Appending $? sends the exit code — your monitoring tool can alert on non-zero exits while still knowing the job ran.

With timeout protection

# Kill the job if it hangs for more than 1 hour
0 2 * * * timeout 3600 /usr/local/bin/backup.sh && curl -fsS https://ping.trebben.dk/YOUR_SLUG

A cron job that hangs indefinitely won't trigger a "missed" alert because it's still running. timeout prevents this.

Silencing curl output

# -f: fail silently on HTTP errors
# -s: silent mode (no progress bar)
# -S: show errors even in silent mode
# --max-time 10: don't let curl hang
curl -fsS --max-time 10 https://ping.trebben.dk/YOUR_SLUG
Tip: Always use --max-time with curl in cron. If the monitoring service is briefly unreachable, you don't want curl blocking your next job.

Systemd timers

If you use systemd timers instead of cron, add the ping as an ExecStartPost directive.

# /etc/systemd/system/backup.service
[Unit]
Description=Nightly backup

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
ExecStartPost=/usr/bin/curl -fsS --max-time 10 https://ping.trebben.dk/YOUR_SLUG

ExecStartPost only runs if ExecStart succeeds. If you want to ping regardless:

# The - prefix means "don't fail the unit if this command fails"
ExecStartPost=-/usr/bin/curl -fsS --max-time 10 https://ping.trebben.dk/YOUR_SLUG

Docker

In a Dockerfile entrypoint

#!/bin/sh
# entrypoint.sh
/app/process-queue.sh
curl -fsS --max-time 10 https://ping.trebben.dk/YOUR_SLUG

In docker-compose with a health check wrapper

# docker-compose.yml
services:
  backup:
    image: your-backup-image
    command: >
      sh -c '/app/backup.sh &&
             curl -fsS --max-time 10 https://ping.trebben.dk/YOUR_SLUG'

Kubernetes CronJobs

apiVersion: batch/v1
kind: CronJob
metadata:
  name: nightly-backup
spec:
  schedule: "0 2 * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: backup
            image: your-backup-image
            command:
            - sh
            - -c
            - |
              /app/backup.sh &&
              curl -fsS --max-time 10 https://ping.trebben.dk/YOUR_SLUG
          restartPolicy: OnFailure

CI/CD pipelines

GitHub Actions

# .github/workflows/deploy.yml
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: ./deploy.sh
      - run: curl -fsS --max-time 10 https://ping.trebben.dk/YOUR_SLUG
        name: Report success to CronPulse

GitLab CI

deploy:
  script:
    - ./deploy.sh
  after_script:
    - curl -fsS --max-time 10 https://ping.trebben.dk/YOUR_SLUG

Scripts and wrappers

If you want to wrap any script with monitoring without modifying the script itself:

#!/bin/sh
# monitor-wrap.sh — wraps any command with heartbeat monitoring
# Usage: monitor-wrap.sh YOUR_SLUG command [args...]

SLUG="$1"
shift

"$@"
EXIT_CODE=$?

curl -fsS --max-time 10 "https://ping.trebben.dk/${SLUG}/${EXIT_CODE}" || true

exit $EXIT_CODE

Then in your crontab:

0 2 * * * /usr/local/bin/monitor-wrap.sh a1b2c3 /usr/local/bin/backup.sh

What to set as your schedule

Set the monitor's expected period to match how often your job runs. Add a grace period for execution time:

The grace period accounts for the time the job itself takes to run. A backup that takes 20 minutes needs at least 20 minutes of grace.

Common mistakes

Getting started

If you want to try this with CronPulse:

  1. Sign up at cronpulse.trebben.dk (free, 20 monitors, no credit card)
  2. Create a monitor with your job's schedule
  3. Copy the ping URL and add it to your job using any pattern above

The free tier is enough for most setups. If you need more than 20 monitors, the pro plan is $5/month.

The full API docs are at cronpulse.trebben.dk/docs.