DevOps Cron Schedule Debugging — Visual Guide
Table of Contents
For DevOps and SRE teams, cron failures typically fall into three categories: the expression is wrong, the timezone is wrong, or the job ran but failed silently. Start every debugging session with a visual check — paste the expression into the crontab visualizer to confirm the schedule is what you intended before investigating the server.
This guide covers the full debugging workflow for cron jobs in production environments, from expression validation to log analysis and monitoring setup.
Step 1: Always Validate the Expression Before the Server
The majority of "my cron job isn't running" issues are expression errors or timezone mismatches. Validate the expression before SSH-ing into anything:
- Copy the cron expression from your crontab, YAML, or config file.
- Paste into the crontab visualizer — see the next 20 run times in your local timezone.
- Ask: "Do these times match what I intended?"
If the visualizer shows runs at unexpected times, the expression is the problem. Fix it there before touching the server.
Common expression mistakes caught at this step:
- Hours in local time when server is UTC (9 AM local ≠ 9 AM UTC)
- OR logic when both day-of-month and day-of-week are set
- Inverted minute/hour fields (common copy-paste error)
- Step expressions with unexpected starting points (
1/15vs*/15)
Step 2: Check Cron Daemon Status and Logs
If the expression is correct but jobs aren't running:
# Is the cron daemon running?
systemctl status cron # Debian/Ubuntu
systemctl status crond # RHEL/CentOS
# Check system logs for cron activity
grep CRON /var/log/syslog | tail -50
journalctl -u cron --since "2 hours ago"
# On macOS
log show --predicate 'subsystem == "com.apple.xpc.launchd"' --last 1h | grep cron
What to look for in the logs:
CMD (command text)— confirms the job triggered and what command ransession opened/closed for user— shows the job ran to completion- No entries at the expected time — job wasn't triggered (expression or daemon issue)
- Error messages in job output — job triggered but failed
Step 3: Capture Job Output to Debug Silent Failures
By default, cron sends job output to the user's local email (via sendmail). On most server environments, this is silently dropped. Always redirect output explicitly:
# Redirect both stdout and stderr to a log file
0 9 * * * /path/to/script.sh >> /var/log/myjob.log 2>&1
# Timestamp each run
0 9 * * * echo "$(date): starting job" >> /var/log/myjob.log 2>&1 && /path/to/script.sh >> /var/log/myjob.log 2>&1
# Suppress all output (only do this if you have other monitoring)
0 9 * * * /path/to/script.sh > /dev/null 2>&1
After adding logging, wait for the next scheduled run, then check the log file. If the file isn't created, the job didn't trigger (cron/expression issue). If it's created but empty or shows an error, the job triggered but the script failed.
Step 4: PATH and Environment Variables in Cron
Cron runs with a minimal environment — PATH is usually just /usr/bin:/bin. Commands that work in your terminal may fail in cron if they require /usr/local/bin, /opt/homebrew/bin, or other custom paths.
Fix: always use absolute paths in cron jobs:
# Instead of:
0 9 * * * python3 script.py
# Use:
0 9 * * * /usr/bin/python3 /home/user/scripts/script.py
# Or set PATH in crontab:
PATH=/usr/local/bin:/usr/bin:/bin
0 9 * * * python3 /home/user/scripts/script.py
Other environment differences between cron and interactive shell:
- HOME is set to the user's home directory (usually correct)
- SHELL defaults to /bin/sh (not bash) — use bash explicitly if needed:
SHELL=/bin/bash - No interactive profile loaded —
.bashrcand.bash_profileare not sourced - Environment variables set in
.bashrcare not available in cron
Setting Up Monitoring and Alerting for Critical Cron Jobs
For production cron jobs, passive logging isn't enough — you need active alerting when jobs miss or fail. Three approaches by complexity:
Simple: health check pings
Services like Healthchecks.io and Cronitor provide a "dead man's switch" — your cron job pings them on success, and they alert you if the ping doesn't arrive on schedule:
# Add a ping to your cron job (replace URL with your check URL)
0 9 * * * /path/to/script.sh && curl -s https://hc-ping.com/your-uuid > /dev/null 2>&1
Medium: log aggregation alerts
If you're already using a log aggregation stack (ELK, Datadog, Splunk), create an alert on "expected log line not seen in last N minutes." More infrastructure to set up but integrates with your existing alerting.
Advanced: systemd timers instead of crontab
For Debian/Ubuntu and RHEL/CentOS servers, systemd timers provide built-in run history (systemctl list-timers), persistent scheduling (catches missed runs after downtime), and integration with journald for structured logging.
Try It Free — No Signup Required
Runs 100% in your browser. No account, no install, no limits.
Open Free Crontab VisualizerFrequently Asked Questions
How do I find out if a cron job is running or if it ran at all?
Check the system log: "journalctl -u cron | grep CMD" or "grep CRON /var/log/syslog" shows every cron job trigger. Look for entries at the time you expected the job to run. If there are no entries, the job didn't trigger — check that the cron daemon is running ("systemctl status cron") and the expression is correct. Also verify crontab was saved correctly with "crontab -l".
My cron job works in the terminal but not in crontab — why?
Almost certainly a PATH issue. Cron uses a minimal PATH (/usr/bin:/bin) while your terminal has a full PATH including /usr/local/bin, user-specific binaries, etc. Fix: use absolute paths for all commands in cron jobs (e.g., /usr/local/bin/node instead of node), or add "PATH=/usr/local/bin:/usr/bin:/bin" at the top of your crontab file.
How do I get alerted when a cron job fails or doesn't run?
Use a dead man's switch monitoring service: add a curl command to your cron job that pings a URL on success. Services like Healthchecks.io (free tier available) or Cronitor alert you if the ping doesn't arrive within the expected window. This is simpler than log-based alerting and works for any cron job with a single line change.

