MeatButton

Cron Job Not Running? Why Your Crontab Isn't Working and How to Fix It

For anyone whose scheduled tasks aren't running

You set up a cron job. You waited. Nothing happened. You waited longer. Still nothing. You checked and double-checked the schedule. You edited the crontab again. You googled it. You asked ChatGPT. It still isn't running.

Cron is one of the most useful tools on a Linux server, and also one of the most frustrating to debug. Here's why it's probably not working, and how to fix it.

What cron actually is

Cron is like an alarm clock for your server. You tell it "run this command every day at 3 AM" or "run this script every five minutes," and it does it automatically in the background. No human needed.

People use cron for things like:

You set it up by editing something called a crontab (cron table) — a list of scheduled commands. You edit it with crontab -e, add a line, save, and cron is supposed to handle the rest.

Supposed to.

The silent failure problem

Here's the thing about cron that nobody warns you about: it fails silently.

When you run a command in your terminal and something goes wrong, you see the error immediately. Red text, a traceback, a "file not found" message — something tells you what happened.

Cron runs in the background with no terminal attached. If your job fails, cron doesn't pop up a notification. It doesn't send you an email (unless you specifically configured that). It doesn't log the error anywhere obvious. It just... doesn't run. Or it runs, hits an error, and quietly moves on.

Your cron job can fail every single day for a month and you would never know unless you set up logging yourself. This is the #1 reason people think cron "isn't working" — it might actually be running and failing, but you can't see the failure.

Crontab syntax: the schedule format

Before we get into debugging, let's make sure the schedule itself is right. Every crontab line has five time fields followed by the command to run:

# minute  hour  day-of-month  month  day-of-week   command
    0       3       *            *        *          /path/to/script.sh

That example means "at minute 0 of hour 3 (3:00 AM), every day of every month, regardless of what day of the week it is, run this script."

Here are a few more examples:

# Every 5 minutes
*/5 * * * * /usr/bin/python3 /home/deploy/check_health.py

# Every Monday at 9 AM
0 9 * * 1 /home/deploy/send_report.sh

# First day of every month at midnight
0 0 1 * * /home/deploy/cleanup.sh

# Every day at 2:30 PM
30 14 * * * /usr/bin/node /home/deploy/myapp/task.js

Common mistakes in the schedule:

If you want to double-check your schedule, search for "crontab guru" — it's a website that lets you paste a cron expression and see in plain English when it will run.

The most common reasons your cron job isn't working

1. The PATH trap

This is the single most common reason cron jobs fail, and it trips up almost everyone.

When you open your terminal and type python3 myscript.py, it works. Your terminal knows where python3 is because your shell has a PATH variable — a list of directories to search for executables. Your terminal might search /usr/local/bin, /usr/bin, /home/youruser/.local/bin, and a dozen other places.

Cron has almost no PATH. Its default PATH is typically just /usr/bin:/bin. That's it. If your Python is installed at /usr/local/bin/python3 or your Node is managed by nvm at some deeply nested path, cron has no idea where to find it.

This is why a command works perfectly in your terminal but silently fails in cron. Cron tries to run python3, can't find it, and gives up.

Fix: Always use full, absolute paths to every executable in your cron jobs. Don't write:

*/5 * * * * python3 myscript.py

Write:

*/5 * * * * /usr/bin/python3 /home/deploy/myscript.py

To find the full path to any command, type which python3 or which node in your terminal. Whatever it prints, use that exact path in your crontab.

2. Relative paths instead of absolute paths

This is the PATH trap's cousin. Even if cron finds your executable, your script might reference files using relative paths like ./data/output.csv or config.json. In your terminal, those resolve relative to whatever directory you're in. In cron, the working directory is typically your home directory or / — not the directory your script lives in.

So your script runs, tries to open ./data/output.csv, can't find it, and fails.

Fix: Use absolute paths everywhere inside your scripts, or cd into the right directory first:

*/5 * * * * cd /home/deploy/myapp && /usr/bin/python3 task.py

3. Your script isn't executable

If your crontab line runs a script directly (not through an interpreter), the script file needs to be marked as executable. If it isn't, cron can't run it.

# This won't work if cleanup.sh isn't executable:
0 3 * * * /home/deploy/cleanup.sh

# Fix: make it executable
chmod +x /home/deploy/cleanup.sh

Alternatively, call the interpreter explicitly and it doesn't matter whether the file is executable:

0 3 * * * /bin/bash /home/deploy/cleanup.sh

4. Wrong user's crontab

Every user on a Linux system has their own crontab. If you set up the job as your regular user but the script needs root permissions, it won't work. If you set it up as root but the script depends on things in your regular user's home directory, that won't work either.

crontab -e edits your current user's crontab. sudo crontab -e edits root's crontab. These are completely separate lists.

Run crontab -l (and sudo crontab -l) to see what's actually scheduled for each user. You might find your job is in the wrong one.

5. Environment variables aren't available

Just like with systemd services, cron runs in a stripped-down environment. It doesn't read your .bashrc, .profile, or .bash_profile. Any environment variables you set in those files — DATABASE_URL, API_KEY, JAVA_HOME — simply don't exist when cron runs your job.

Fix: Either set the variables directly in the crontab (before your job lines):

DATABASE_URL=postgres://user:pass@localhost/mydb
API_KEY=abc123

*/5 * * * * /usr/bin/python3 /home/deploy/myapp/task.py

Or source your profile at the start of the command:

*/5 * * * * source /home/deploy/.profile && /usr/bin/python3 /home/deploy/myapp/task.py

6. Syntax errors in the crontab

Cron is unforgiving about formatting. A stray character, a missing field, or a typo in the schedule will cause the entire line to be ignored — silently, of course.

Common syntax problems:

Fix: If your command contains a % sign (common in date formats), escape it with a backslash:

# Wrong — cron will break on the %
0 3 * * * /usr/bin/python3 /home/deploy/backup.py --date=$(date +%Y-%m-%d)

# Right — escape the percent signs
0 3 * * * /usr/bin/python3 /home/deploy/backup.py --date=$(date +\%Y-\%m-\%d)

How to debug a cron job that isn't running

Step 1: Verify your crontab is saved

Run crontab -l and make sure your job is actually listed. If it isn't there, you either edited the wrong user's crontab, or your edits didn't save (some editors are finicky with the temp file cron uses).

Step 2: Check the cron log

Most Linux systems log cron activity. Check one of these:

# On Ubuntu/Debian
grep CRON /var/log/syslog

# On CentOS/RHEL/Fedora
cat /var/log/cron

This will show you whether cron even attempted to run your job. If you see entries like CMD (/home/deploy/myscript.sh), cron ran it — the problem is inside the script. If you see nothing, cron never tried, which means the schedule or syntax is wrong.

Step 3: Redirect output to a log file

This is the most important debugging step. Add output redirection to your cron command so you can see what happened:

*/5 * * * * /usr/bin/python3 /home/deploy/task.py >> /home/deploy/cron.log 2>&1

The >> appends stdout to the log file. The 2>&1 redirects stderr (error messages) to the same file. Now when the job runs, any output or errors get written to cron.log where you can actually read them.

After the next scheduled run, check the log:

cat /home/deploy/cron.log

If the file is empty, the job didn't run at all. If it has error messages, you know exactly what to fix.

Step 4: Test the exact command manually

Copy the command from your crontab and run it in a terminal that mimics cron's environment:

env -i /bin/sh -c '/usr/bin/python3 /home/deploy/task.py'

The env -i clears all environment variables, giving you a blank slate similar to what cron uses. If the command fails here, it will fail in cron too.

Why AI gives bad cron advice

If you asked an AI to set up a cron job, there's a good chance it gave you something that doesn't work. Here's what it typically gets wrong:

Cron is one of those tools where the gap between "looks correct" and "actually works" is huge. And that gap is exactly where AI falls short — it generates things that look right but can't verify they actually work on your specific system.

A complete, working cron setup

Here's what a properly configured cron job looks like, with all the common pitfalls addressed:

# Edit your crontab
crontab -e

# Add this line (runs every day at 3 AM):
0 3 * * * cd /home/deploy/myapp && /usr/bin/python3 /home/deploy/myapp/backup.py >> /home/deploy/myapp/cron.log 2>&1

What this does right:

After saving, verify it's there:

crontab -l

Then wait for the scheduled time, and check your log file. If the log has output, your job is running. If the log has errors, you know what to fix. If the log doesn't exist, go back to the cron system log and see if cron even attempted to run it.

Cron still not cooperating?

Silent failures are the worst kind. You can't fix what you can't see, and cron is designed to hide its problems from you. Press the MeatButton and a real expert will look at your crontab, check the logs, trace the PATH, and get your job running. The first one is free.

Get MeatButton