Skip to content
Back to Blog
Linux & Server10 min read

Common Cron Job Mistakes in Linux and How to Avoid Them

Cron jobs fail for predictable reasons: path issues, permissions errors, timezone confusion, and syntax mistakes. Learn the most common pitfalls and the correct approach to building reliable scheduled tasks.

Written by Abdul AbrorTechnical Hosting Support Engineer
Common Cron Job Mistakes in Linux and How to Avoid Them
On this page

Cron is one of the oldest and most reliable tools for scheduling recurring tasks on Linux systems, yet it trips up even experienced administrators. The minimal environment cron provides, its terse syntax, and silent failure modes create a perfect storm for subtle bugs. Most cron job failures share the same handful of root causes. This guide catalogues the most common mistakes and shows you the correct approach for each.

Mistake 1: Assuming the Full Shell Environment

The most frequent cause of "works manually, fails in cron" problems is environment mismatch. When you run a command from your shell, you inherit a rich environment: PATH includes /usr/local/bin, /usr/bin, your home bin directory, and more. Environment variables like LANG, USER, HOME, and application-specific settings are all loaded from your shell profile.

Cron jobs run in a stripped-down environment. The PATH is typically just /usr/bin:/bin, and most environment variables you expect are missing.

The Problem:

30 2 * * * backup-script.sh

This fails silently because backup-script.sh is not in /usr/bin or /bin, and cron cannot find it.

The Correct Approach:

Use absolute paths for all executables and scripts:

30 2 * * * /home/username/scripts/backup-script.sh

For complex jobs, explicitly set the environment at the top of your crontab:

PATH=/usr/local/bin:/usr/bin:/bin
SHELL=/bin/bash
HOME=/home/username

30 2 * * * /home/username/scripts/backup-script.sh

Or source your profile inside the script itself:

#!/bin/bash
source ~/.bashrc
# rest of script

When troubleshooting, add env > /tmp/cron-env.txt as a test job to see exactly what environment cron provides.

Mistake 2: Ignoring Script Permissions and Ownership

Cron respects Unix file permissions. If the script is not executable or owned by the wrong user, the job will fail.

The Problem:

You create a script, add it to your crontab, and nothing happens. The script has mode 644 (read/write for owner, read for others) but no execute bit.

The Correct Approach:

Make scripts executable:

chmod +x /home/username/scripts/backup-script.sh

Verify ownership matches the crontab owner:

ls -l /home/username/scripts/backup-script.sh

If you are editing the user crontab with crontab -e, the job runs as that user. If you are editing /etc/crontab or files in /etc/cron.d/, you must specify the user column explicitly:

30 2 * * * username /home/username/scripts/backup-script.sh

For system-wide jobs that need root privileges, use sudo crontab -e or place them in /etc/cron.d/ with the user set to root.

Mistake 3: Redirecting Output Incorrectly or Not at All

By default, cron emails the output of each job to the user's local mail account. On many modern systems, local mail is not configured, so output vanishes. You lose error messages, making debugging nearly impossible.

The Problem:

30 2 * * * /home/username/scripts/backup-script.sh

Errors are mailed to username@localhost, but you never check local mail.

The Correct Approach:

Explicitly redirect output to a log file:

30 2 * * * /home/username/scripts/backup-script.sh >> /home/username/logs/backup.log 2>&1

This appends stdout and stderr to the log file, preserving error messages and command output.

For jobs that should be silent when successful, redirect to /dev/null but keep errors:

30 2 * * * /home/username/scripts/backup-script.sh > /dev/null 2>> /home/username/logs/backup-errors.log

To use the system logger instead:

30 2 * * * /home/username/scripts/backup-script.sh 2>&1 | logger -t backup-job

Errors will appear in /var/log/syslog or /var/log/messages depending on your distribution.

Mistake 4: Misunderstanding Cron Syntax

Cron's five-field time specification is terse and unforgiving. Mistakes in field order, range syntax, or step values break silently.

The Problem:

Wanting a job every 15 minutes, you write:

* */15 * * * /home/username/scripts/check-status.sh

This runs every minute during the 0th, 15th, 30th, and 45th minutes of each hour—far more often than intended.

The Correct Approach:

Understand the field positions: minute, hour, day-of-month, month, day-of-week.

For every 15 minutes:

*/15 * * * * /home/username/scripts/check-status.sh

For specific times, list them explicitly:

0,15,30,45 * * * * /home/username/scripts/check-status.sh

For ranges and steps:

  • 0-23 means every value from 0 through 23
  • */5 means every 5th value (0, 5, 10, ...)
  • 1-5 in the day-of-week field means Monday through Friday
  • Both day-of-month and day-of-week are ORed together, not ANDed—if either matches, the job runs

When in doubt, test your cron expression with an online validator or use systemd timers with their more explicit OnCalendar syntax.

Mistake 5: Overlapping Job Execution

If a job takes longer to run than its interval, multiple instances may overlap, causing race conditions, lock contention, or resource exhaustion.

The Problem:

*/5 * * * * /home/username/scripts/slow-report.sh

The script takes 8 minutes to complete. At minute 5, a second instance starts while the first is still running. At minute 10, a third starts. You quickly have resource contention.

The Correct Approach:

Use a lock file to prevent overlapping runs:

#!/bin/bash
LOCKFILE="/var/lock/slow-report.lock"

if [ -e "$LOCKFILE" ]; then
    echo "Job already running, exiting."
    exit 0
fi

trap "rm -f $LOCKFILE" EXIT
touch "$LOCKFILE"

# actual work here

Or use flock for atomic locking:

*/5 * * * * flock -n /var/lock/slow-report.lock /home/username/scripts/slow-report.sh

The -n flag makes flock exit immediately if the lock is held, preventing queued jobs.

For more complex workflows, consider moving to systemd timers with Persistent=true and OnCalendar expressions that naturally prevent overlap.

Mistake 6: Not Accounting for Timezone and Daylight Saving Time

Cron uses the system timezone. During daylight saving time transitions, jobs scheduled at the transition hour may run twice, skip entirely, or shift unexpectedly.

The Problem:

You schedule a job at 2:00 AM. In spring, the clock jumps from 2:00 AM to 3:00 AM—your job never runs. In fall, 2:00 AM happens twice—your job runs twice.

The Correct Approach:

Avoid scheduling critical jobs at DST transition hours (typically 2:00-3:00 AM in regions observing DST). Schedule them at 1:00 AM or 4:00 AM instead.

For timezone-sensitive jobs, explicitly set TZ in the crontab:

TZ=America/New_York
30 2 * * * /home/username/scripts/backup-script.sh

Or set it inside the script:

#!/bin/bash
export TZ=America/New_York
# rest of script

For UTC-based scheduling that ignores local DST:

TZ=UTC
30 6 * * * /home/username/scripts/backup-script.sh

Systemd timers offer finer control with OnCalendar expressions and explicit timezone handling.

Mistake 7: Editing the Wrong Crontab

Linux has user crontabs, the root crontab, /etc/crontab, and per-directory crontabs in /etc/cron.d/. Editing the wrong one leads to jobs not running or running with incorrect privileges.

The Problem:

You edit your user crontab with crontab -e, but the job needs root access. It runs, fails silently due to permissions, and you waste time debugging the script instead of the privilege level.

The Correct Approach:

Use the correct crontab for the task:

  • crontab -e edits the current user's crontab
  • sudo crontab -e edits root's crontab
  • /etc/crontab is the system-wide crontab (includes a user column)
  • /etc/cron.d/ holds drop-in crontab files (also include a user column)

System-wide crontabs use six-field syntax:

30 2 * * * root /usr/local/bin/system-maintenance.sh

User crontabs use five-field syntax (no user column).

To list a user's crontab:

crontab -l

To list root's crontab:

sudo crontab -l

To list all system cron jobs:

ls -l /etc/cron.d/
cat /etc/crontab

Mistake 8: Using Percent Signs Without Escaping

The percent sign % has special meaning in crontab: it signals the start of stdin for the command. Unescaped percent signs break commands that use them, like date formatting.

The Problem:

30 2 * * * /usr/bin/mysqldump mydb > /backup/mydb-$(date +%Y%m%d).sql

The %Y%m%d is interpreted as stdin, and the job fails.

The Correct Approach:

Escape percent signs with backslashes:

30 2 * * * /usr/bin/mysqldump mydb > /backup/mydb-$(date +\%Y\%m\%d).sql

Or move the command into a script:

#!/bin/bash
DATESTAMP=$(date +%Y%m%d)
/usr/bin/mysqldump mydb > /backup/mydb-$DATESTAMP.sql

Then call the script from cron:

30 2 * * * /home/username/scripts/backup-mysql.sh

Mistake 9: Not Monitoring Job Success or Failure

Cron jobs run silently in the background. Unless you actively check logs or monitor exit codes, failures go unnoticed until something breaks.

The Problem:

Your backup job has been failing for three weeks. You only discover it when you need to restore and have no recent backups.

The Correct Approach:

Log job execution with timestamps:

#!/bin/bash
LOGFILE="/home/username/logs/backup.log"
echo "[$(date)] Starting backup" >> "$LOGFILE"
/usr/bin/rsync -a /data /backup/ >> "$LOGFILE" 2>&1
if [ $? -eq 0 ]; then
    echo "[$(date)] Backup completed successfully" >> "$LOGFILE"
else
    echo "[$(date)] Backup failed" >> "$LOGFILE"
    exit 1
fi

Send alerts on failure:

#!/bin/bash
/home/username/scripts/backup-script.sh
if [ $? -ne 0 ]; then
    echo "Backup failed on $(hostname)" | mail -s "Backup Failure" [email protected]
fi

Or use a monitoring service: configure cron to POST to a healthcheck endpoint on success:

30 2 * * * /home/username/scripts/backup-script.sh && curl -fsS --retry 3 https://monitoring-service.example.com/ping/backup-job-id > /dev/null

Services like Cronitor, Healthchecks.io, or UptimeRobot can alert you when expected jobs do not check in.

Mistake 10: Forgetting That Cron Runs Non-Interactively

Cron jobs cannot prompt for input, display GUI elements, or assume a terminal. Scripts that work interactively break when run by cron.

The Problem:

Your script uses sudo without NOPASSWD, or calls a command that expects terminal input. Cron hangs or fails.

The Correct Approach:

Ensure all commands are non-interactive. For sudo, add NOPASSWD entries to /etc/sudoers for specific commands:

username ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart myapp

For commands expecting a TTY, use the -n flag if available, or redirect stdin:

30 2 * * * /home/username/scripts/backup-script.sh < /dev/null

Avoid relying on shell aliases, functions, or interactive prompts. If a command needs confirmation, use flags like -y, --yes, or --force.

Troubleshooting Checklist

When a cron job fails, work through this checklist:

  1. Check cron is running: systemctl status cron or systemctl status cronie
  2. Verify the crontab syntax: crontab -l and inspect the entry
  3. Test the command manually with the same user and minimal environment: env -i HOME=/home/username PATH=/usr/bin:/bin /bin/bash /home/username/scripts/backup-script.sh
  4. Check the logs: /var/log/syslog, /var/log/cron, or your custom log file
  5. Verify file permissions and ownership: ls -l on the script
  6. Check for lock file conflicts if using flock or custom locking
  7. Confirm the timezone and schedule: date and timedatectl
  8. Look for output redirection issues: ensure logs are writable
  9. Test the cron schedule in isolation: use an online cron expression tester
  10. Add a test job that just logs the environment to confirm cron is running at all

Conclusion

Most cron job failures stem from a small set of predictable mistakes: assuming the shell environment, ignoring permissions, misunderstanding syntax, and failing to log or monitor execution. By using absolute paths, redirecting output, testing in a minimal environment, and implementing proper locking and monitoring, you can build reliable scheduled tasks that run without surprises. Cron's simplicity is its strength, but only when you respect its constraints.

FAQ

Why does my cron job work when I run it manually but fail in cron?

The most common cause is environment mismatch. Your shell has PATH, environment variables, and sourced profiles that cron does not. Use absolute paths, set required variables at the top of your crontab, or source your profile inside the script.

How do I see output and errors from my cron job?

Redirect stdout and stderr to a log file in your cron entry: >> /path/to/logfile 2>&1. Check /var/log/syslog or /var/log/cron for cron daemon messages. Set up local mail delivery or use an external monitoring service for alerts.

Can I schedule a job to run every second?

No. Cron's finest granularity is one minute. For sub-minute scheduling, use systemd timers with OnUnitActiveSec or write a daemon that sleeps between runs.

How do I prevent overlapping cron job executions?

Use flock with the -n flag: flock -n /var/lock/myjob.lock /path/to/script.sh. Or implement a lock file check at the start of your script and exit early if the lock exists.

What is the difference between user crontabs and /etc/crontab?

User crontabs are per-user, edited with crontab -e, and use five-field syntax. /etc/crontab and files in /etc/cron.d/ are system-wide, use six-field syntax (including a user column), and typically require root privileges to edit.