/
July 24, 20267 min read

Why Cron Jobs Fail Silently (And How to Actually Catch It)

?missed run, no alert —discovered days later

Cron's biggest design flaw isn't syntax — it's that a cron job can fail completely and nothing will tell you. No dashboard lights up, no notification fires, the job simply doesn't run, or runs and errors out, and the only sign is something downstream quietly breaking hours or days later. This is the single most common way cron burns people in production, and it's almost never a syntax problem.

The environment is not what you think it is

Every cron horror story starts the same way: a script that works perfectly when you run it by hand, and fails silently the moment cron runs it instead. The reason is almost always environment. Cron jobs execute with a minimal shell environment — often just PATH=/usr/bin:/bin — with none of the environment variables, aliases, or shell configuration your interactive login shell quietly loads for you.

A script that calls python3 and works fine in your terminal can fail under cron because the cron environment's PATH doesn't include the pyenv, conda, or nvm shim your interactive shell adds automatically. The fix is almost always the same: use absolute paths for every binary and every file the script touches, and explicitly set any environment variables the job depends on inside the crontab or the script itself, rather than assuming they'll be inherited.

Explicit paths and environment, not inherited assumptions
0 2 * * * PATH=/usr/local/bin:/usr/bin:/bin /home/user/scripts/backup.sh >> /var/log/backup.log 2>&1

Output goes nowhere unless you send it somewhere

By default, cron tries to email any output a job produces to the crontab's owner — and on most modern servers, there's no mail transport agent configured to actually deliver that email, so the output is simply dropped. A script that prints a clear, useful error message on failure is, by default, printing that message into a void.

The fix is cheap and should be the default for every cron entry you write: redirect both stdout and stderr somewhere you'll actually see. A log file with rotation is the minimum bar; piping failures to a monitoring or alerting endpoint is the real fix for anything that matters.

A missed run looks identical to a successful one

This is the part that actually causes production incidents: cron has no concept of 'this job should have run by now and didn't.' If the server was rebooting, the previous invocation was still running, or the daemon itself was briefly down, the scheduled run is simply skipped — with zero indication anywhere that anything went wrong. From the outside, a skipped run and a successful no-op run look exactly the same: nothing happened.

This is why 'the cron job is in the crontab' is never sufficient evidence that a critical job is actually running on schedule. Anything that matters — backups, billing runs, data syncs — needs an independent way to confirm it actually happened, not just that it was scheduled to happen.

The fix: make absence visible

The practical pattern that solves all three of the above at once is a 'dead man's switch': have the job itself ping an external monitoring endpoint on successful completion, and configure that monitor to alert you if the ping doesn't arrive within the expected window. This flips the failure mode from 'silence means nothing is wrong' to 'silence means something is wrong' — the only version of that sentence that's actually useful.

You don't need heavy infrastructure for this. A single request to a heartbeat URL at the end of a script, paired with almost any uptime-style monitoring tool, closes the biggest gap in cron's design without touching the scheduling syntax at all.

Heartbeat ping fired only on success
0 2 * * * /opt/scripts/backup.sh && curl -fsS -m 10 https://heartbeat.example.com/ping/backup-job

Key Takeaways

  • Always use absolute paths and explicitly set required environment variables — never assume cron inherits your shell's setup.
  • Redirect stdout and stderr on every cron entry, even ones you think will never fail.
  • For anything business-critical, add a heartbeat ping so a missed run becomes a loud alert instead of silence.
  • Check the system log (journalctl -u cron, or /var/log/syslog) as your first troubleshooting step — cron logs every invocation attempt, whether it succeeded or not.

Frequently Asked Questions

Why does my cron job work when I run it manually but not under cron?
Almost always an environment difference — cron's PATH and environment variables are minimal compared to your interactive shell. Use absolute paths and explicitly set anything the job depends on.
How do I know if cron actually attempted to run my job?
Check your system log — /var/log/syslog, /var/log/cron, or journalctl -u cron depending on your distro — which records every invocation attempt regardless of whether the job itself succeeded.
What's the simplest way to get alerted when a cron job fails?
Add a heartbeat ping — a single request to a monitoring service — at the end of the job, triggered only on success. If the ping doesn't arrive on schedule, the monitoring service alerts you, with no extra infrastructure needed.