June 14, 2026 6 min read
How to monitor bash cron scripts properly
Checking exit codes in bash cron scripts is not enough. Here's how to detect missed runs, hung scripts, and silent failures in shell-based scheduled jobs — without setting up a logging pipeline.
Bash cron scripts are the most common form of scheduled job in production infrastructure. Database backups, log rotation, S3 syncs, disk cleanup — most of these start life as a shell script and a crontab entry.
They're also among the least monitored. The conventional approach — redirecting output to a log file and checking for error strings — requires someone to read the log, which means nobody reads it until something breaks. Exit code checks catch crashes. They don't catch missed runs (the server rebooted and the crontab was lost), hung scripts (the rsync is still running three days later), or silent failures (the backup ran but produced an empty archive).
Here's how to add proper monitoring to bash cron scripts without a logging pipeline.
The minimal pattern
Add three curl calls to any bash script: one at the start, one on success, one on failure.
#!/bin/bash
set -euo pipefail
MONITOR_ID="your-monitor-id"
API_KEY="${CRONTIFY_API_KEY}"
BASE_URL="https://api.crontify.com/api/v1/ping/${MONITOR_ID}"
# Send a monitoring ping — never fails the script
ping() {
curl -fsS -X POST \
-H "X-API-Key: ${API_KEY}" \
"${BASE_URL}/${1}" \
${2:+-H "Content-Type: application/json" -d "${2}"} \
> /dev/null 2>&1 || true
}
ping "start"
# Your job logic goes here
result=$(your_job_command 2>&1)
exit_code=$?
if [ $exit_code -eq 0 ]; then
ping "success"
else
# Send failure with the last 2000 chars of output as log
log_payload=$(printf '{"message":"Exit code %s","log":"%s"}' \
"$exit_code" \
"$(echo "$result" | tail -c 2000 | sed 's/"/\\"/g' | tr '\n' ' ')")
ping "fail" "$log_payload"
exit $exit_code
fi
The || true at the end of the curl call is critical. It prevents a failed ping (network timeout, Crontify outage) from causing the script to exit non-zero. Your monitoring must never be in the critical path of your actual job.
Attaching output count for silent failure detection
For scripts that process data, attach a record count to the success ping. This enables alert rules in Crontify's dashboard that fire when the script ran but processed nothing.
#!/bin/bash
set -euo pipefail
MONITOR_ID="your-monitor-id"
API_KEY="${CRONTIFY_API_KEY}"
BASE_URL="https://api.crontify.com/api/v1/ping/${MONITOR_ID}"
ping() {
curl -fsS -X POST \
-H "X-API-Key: ${API_KEY}" \
"${BASE_URL}/${1}" \
${2:+-H "Content-Type: application/json" -d "${2}"} \
> /dev/null 2>&1 || true
}
ping "start"
# Sync files and capture count
FILE_COUNT=$(aws s3 sync /data s3://your-bucket/backup \
--exclude "*.tmp" \
2>&1 | grep -c "upload:" || true)
BACKUP_SIZE=$(du -sb /data | cut -f1)
# Send success with metadata
ping "success" "{\"meta\":{\"files_synced\":${FILE_COUNT},\"backup_bytes\":${BACKUP_SIZE}}}"
In Crontify's dashboard, define rules:
files_synced eq 0→ alert (nothing was synced despite running)backup_bytes lt 1000→ alert (backup archive is suspiciously small)
A reusable wrapper for multiple scripts
If you have several cron scripts to instrument, a sourced helper avoids repeating the curl logic in every script:
# /usr/local/lib/crontify.sh
CRONTIFY_BASE_URL="https://api.crontify.com/api/v1/ping"
crontify_ping() {
local monitor_id="$1"
local event="$2"
local payload="${3:-}"
curl -fsS -X POST \
-H "X-API-Key: ${CRONTIFY_API_KEY}" \
${payload:+-H "Content-Type: application/json" -d "${payload}"} \
"${CRONTIFY_BASE_URL}/${monitor_id}/${event}" \
> /dev/null 2>&1 || true
}
# Run a command with monitoring
# Usage: crontify_wrap <monitor_id> <command> [metadata_json]
crontify_wrap() {
local monitor_id="$1"
shift
crontify_ping "$monitor_id" "start"
local output
local exit_code=0
output=$("$@" 2>&1) || exit_code=$?
if [ $exit_code -eq 0 ]; then
crontify_ping "$monitor_id" "success"
else
local log_excerpt
log_excerpt=$(echo "$output" | tail -c 2000 | sed 's/"/\\"/g' | tr '\n' ' ')
crontify_ping "$monitor_id" "fail" \
"{\"message\":\"Exit code ${exit_code}\",\"log\":\"${log_excerpt}\"}"
fi
return $exit_code
}
Usage in any script:
#!/bin/bash
source /usr/local/lib/crontify.sh
crontify_wrap "your-monitor-id" /usr/local/bin/your-backup-script.sh
Environment variables in crontab
Cron runs with a minimal environment. Variables you set in your shell profile are not available to cron jobs. Set environment variables explicitly in the crontab:
CRONTIFY_API_KEY=ck_live_your_key
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
Nightly backup
0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
Or load them from a file at the start of your script:
# Load environment
set -a
source /etc/cron-env
set +a
Keep /etc/cron-env readable only by root or the cron user:
chmod 600 /etc/cron-env
Common failure modes this catches
Server reboot without crontab restore. The system crontab is restored on boot, but if you manually added jobs to a user crontab and that user's crontab wasn't backed up, the jobs stop running. The missed start ping alerts within the grace period.
Script hanging on a network call. rsync, aws s3 sync, or curl to a slow endpoint can hang indefinitely if no timeout is configured. The start ping arrives; the success ping doesn't. Crontify fires a hung job alert after the maximum duration threshold.
S3 sync with zero files. The bucket exists and the command succeeds, but the source directory was empty or the filter excluded everything. The files_synced eq 0 rule catches this.
Backup producing an empty archive. tar exits 0 even if it compressed nothing. The backup_bytes lt 1000 rule catches an archive that's smaller than any real backup should be.
Crontify is free for up to 5 monitors — no credit card required. HTTP pings work in any shell script without installing any additional packages.
Start monitoring your scheduled jobs
Free plan includes 5 monitors. No credit card required. Up and running in under 5 minutes.
Get started free →More from the blog
June 17, 2026 6 min read
Free cron job monitoring tools compared
There are several free cron job monitoring tools, but they differ significantly in what they actually catch. Here's an honest comparison of the free tiers available in 2026.
Read more →
June 14, 2026 7 min read
Monitoring Kubernetes CronJobs with HTTP pings
kube-state-metrics and Prometheus work for Kubernetes CronJob monitoring, but they're complex to set up and fail along with the cluster. Here's a simpler, cluster-independent approach using HTTP pings.
Read more →