Blog
Wild & Free Tools

DevOps Cron Schedule Debugging — Visual Guide

Last updated: April 2026 8 min read

Table of Contents

  1. Step 1: Validate the Expression First
  2. Step 2: Check Cron Daemon Status and Logs
  3. Step 3: Capture Job Output
  4. Step 4: Common PATH and Environment Issues
  5. Setting Up Cron Job Monitoring
  6. Frequently Asked Questions

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:

  1. Copy the cron expression from your crontab, YAML, or config file.
  2. Paste into the crontab visualizer — see the next 20 run times in your local timezone.
  3. 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:

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:

Sell Custom Apparel — We Handle Printing & Free Shipping

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:

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 Visualizer

Frequently 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.

Launch Your Own Clothing Brand — No Inventory, No Risk