On this page
Why Do Cron Jobs Fail Silently (and How to Fix It)?
Your cron job ran, output nothing, and did nothing. Here's why every cron job fails silently by default — and exactly how to fix it with logging, exit codes, managed schedulers, and external monitoring.
Quick answer
- Cron fails silently because its only feedback mechanism is email, which most servers never deliver.
- The fix: redirect output to a log file, make your scripts exit non-zero on failure, and add external monitoring.
- Or skip bare cron entirely and use a managed scheduler with built-in alerts.
Why “it ran” doesn’t mean “it worked”
A cron job that runs and exits 0 looks healthy to cron. But exiting 0 doesn’t mean the job did its work. A backup script that created an empty file, a cleanup job that skipped the directory because the mount wasn’t ready, a health check that ran curl without checking the response code — all exit 0. All failures.
The cron daemon has exactly one feedback mechanism: email. If the job produces any stdout or stderr output, cron emails it to the crontab owner. On a typical cloud server, that email has nowhere to go. sendmail isn’t configured. The mail spool fills up. Nobody reads it.
So the job fails. Cron knows. Nobody else does.
The four failure modes (and the fix for each)
1. The script failed but cron doesn’t know
The script ran, hit an error, and… kept going. It didn’t set -e. It didn’t check return codes. It just plowed through and exited 0 at the end.
# This exits 0 whether curl succeeded or not
curl https://api.example.com/healthThe fix: Exit non-zero on any failure.
#!/bin/bash
set -euo pipefail
# Now curl failing stops the script and exits non-zero
curl --fail https://api.example.com/health || exit 1Add set -e (exit on error), set -u (error on undefined variables), and set -o pipefail (fail if any command in a pipe fails) to every cron script. If the script exits non-zero, cron captures the output and at least tries to email it.
2. The script produced output but nobody saw it
The job ran, produced error output, and cron tried to email it — to an address that doesn’t deliver. The errors are lost.
The fix: Redirect output to a log file.
# In crontab:
30 2 * * * /home/me/backup.sh >> /var/log/backup.log 2>&1>> /var/log/backup.log 2>&1 appends both stdout and stderr to a file. Now there’s a record. Even better: use logger to send output to syslog, where your monitoring stack can pick it up.
3. Cron’s environment is different from yours
This is the #1 “works in my terminal, fails in cron” cause. Cron runs with a stripped-down environment — no PATH, no HOME in some implementations, no shell profile loaded. Your script can’t find python3, pg_dump, or any other command that relies on PATH.
The fix: Use absolute paths everywhere.
# Broken (cron can't find these):
python3 /home/me/backup.py
pg_dump mydb > backup.sql
# Fixed:
/usr/bin/python3 /home/me/backup.py
/usr/bin/pg_dump mydb > /home/me/backup.sqlAlso set PATH explicitly at the top of your crontab or script:
PATH=/usr/local/bin:/usr/bin:/bin
SHELL=/bin/bash
HOME=/home/me4. Nobody checked whether the job ran at all
This is the most common failure mode in practice: the job stopped running weeks ago and nobody noticed. Maybe the server was rebuilt and the crontab wasn’t restored. Maybe a disk filled up and cron couldn’t write its lock file. Maybe someone commented out the line during debugging and forgot to uncomment it.
Cron has no built-in “this job hasn’t run in X hours” alert. You won’t know until you notice the stale backups or the overflowing log directory.
The fix: External monitoring.
The simplest approach is a heartbeat monitor like healthchecks.io. Your cron job pings a URL at the end of a successful run. If the ping doesn’t arrive on schedule, the service alerts you. This catches “didn’t run” (the server is down, the crontab was removed) and “ran but failed” (script exited non-zero before the ping line).
#!/bin/bash
set -euo pipefail
/usr/bin/python3 /home/me/backup.py
# Only ping on success:
curl --fail --silent https://hc-ping.com/your-uuid-hereFor more detail, see the full How to Monitor Your Cron Jobs guide.
The cleanest solution: don’t use bare cron
For anything that matters — backups, billing runs, data cleanup — a managed scheduler is worth the few minutes of setup:
| Option | What it gives you |
|---|---|
| GitHub Actions scheduled workflows | Built-in logging, failure notifications, retry, Git-tracked config |
| AWS EventBridge Scheduler | Retries, dead-letter queues, CloudWatch integration |
| healthchecks.io | Heartbeat monitoring for any cron job, free tier, SMS/email/Slack alerts |
| cron-job.org | Free hosted cron with dashboard, email alerts on failure |
Each of these alerts you when a job fails or doesn’t run. Bare cron never will.
Where this bites vibecoders
AI assistants generate
crontab -eentries constantly — for backups, scrapers, cleanup scripts, report generation. They never addset -e, never redirect output to a log, never suggest a heartbeat monitor. The first cron job a vibecoder ships is a database backup that silently stops working the night the disk fills up, and they find out three weeks later when they need the backup.
Where AI coding assistants get this wrong
- Writing
0 * * * * python3 script.pywith no absolute path, no logging, no error handling. - Never suggesting
set -eor exit-code checking. - Generating cron jobs with no monitoring hook — the assistant treats “scheduled” as “done.”
- Picking random times (e.g., midnight UTC is peak load on many services).
Checklist
-
set -euo pipefailat the top of every cron script - Absolute paths for every command
- Output redirected to a log file:
>> /var/log/jobname.log 2>&1 - Script exits non-zero on failure
- Heartbeat ping (healthchecks.io or equivalent) at end of successful run
- Consider a managed scheduler for anything that matters
FAQ
Why does my script work in the terminal but fail in cron?
Cron runs with a nearly empty environment — no PATH, no HOME in some cases, no shell aliases. Use absolute paths for every command (e.g., /usr/bin/python3 not python3) and set PATH and SHELL at the top of your script.
How do I get cron to email me when a job fails?
Set MAILTO=you@example.com at the top of your crontab. Then make sure your script exits non-zero on failure (exit 1). Cron only emails on output — silence plus exit 0 means no email, even if the job did nothing.
What’s the difference between this and heartbeat monitoring?
Heartbeat monitoring tells you the job ran. Exit codes tell you it ran correctly. You need both: exit non-zero for “ran but failed” and ping a heartbeat service for “didn’t run at all.” Exiting non-zero without a heartbeat means you still won’t know if the server was down.
Related topics
- What Is a Cron Job (and Why Do They Fail Silently)?
- How to Monitor Your Cron Jobs
- What Is Uptime Monitoring?
- What Is a Dead Letter Queue?
- What Is Log Rotation (and Why Do Your Logs Keep Disappearing)?