Home › Cron Job Not Running
Cron Job Not Running
A step-by-step guide to debugging a cron job that silently does nothing. Covers the most common causes: PATH, permissions, environment variables, output logging, timezone and missing trailing newline.
Step 1: Is Cron Actually Running?
The first thing to check - before anything else. If the cron daemon is not running, nothing fires.
Debian / Ubuntu
systemctl status cron
If inactive: sudo systemctl start cron && sudo systemctl enable cron
CentOS / RHEL / AlmaLinux
systemctl status crond
If inactive: sudo systemctl start crond && sudo systemctl enable crond
Step 2: Did Cron Actually Fire the Job?
There is a difference between cron not running the job and the job running but failing. Check the cron log first.
Debian / Ubuntu
grep CRON /var/log/syslog | tail -30 # live: tail -f /var/log/syslog | grep CRON
Look for a line with CMD - that means cron fired it. If no CMD line appears, cron is not seeing the job at all.
CentOS / RHEL
tail -30 /var/log/cron # live: tail -f /var/log/cron
Same logic - CMD in the log means cron fired it. No CMD means cron cannot see the entry.
With journald (any distro)
journalctl -u cron --since "1 hour ago" # Debian/Ubuntu journalctl -u crond --since "1 hour ago" # CentOS/RHEL
Step 3: Enable Output Logging
By default, cron discards all output from your script. If the job fires but nothing happens, you have no way to know what went wrong - unless you redirect output to a file.
0 2 * * * /usr/bin/php /var/www/script.php >> /var/log/myjob.log 2>&1
The >> appends stdout to the log file. The 2>&1 also redirects stderr (error messages) to the same file.
Without 2>&1 you only see normal output - exceptions, fatal errors and warnings are silently discarded.
Once you have a log file, tail -f /var/log/myjob.log shows you exactly what the script is doing.
Step 4: Test the Command Manually
Copy the exact command from the crontab and run it in the terminal, as the same user cron will use. If it fails here, it fails in cron too.
# Run as yourself: /usr/bin/php /var/www/script.php # Run as the cron user (e.g. www-data): sudo -u www-data /usr/bin/php /var/www/script.php
Running as the correct user is important - file permissions and home directory differ per user.
A script that works as root may fail as www-data.
Step 5: The PATH Problem
This is the single most common cause of cron jobs that work manually but fail silently.
Cron runs with a minimal environment: PATH=/usr/bin:/bin.
Your shell has ~/.bashrc loaded with /usr/local/bin, ~/bin, rbenv, nvm and everything else.
Cron has none of that.
Solution 1: Use full paths
# Find the full path: which php which python3 which node # Use it in the crontab: 0 2 * * * /usr/bin/php /var/www/script.php
Solution 2: Set PATH in the crontab
PATH=/usr/local/bin:/usr/bin:/bin HOME=/home/username 0 2 * * * php /var/www/script.php
Variables defined at the top of the crontab apply to all jobs in that file.
Step 6: Environment Variables
Cron does not load ~/.bashrc, ~/.profile or any shell initialization file.
Database credentials, API keys, DJANGO_SETTINGS_MODULE, NODE_ENV - none of them are available unless you set them explicitly.
In the crontab
APP_ENV=production DB_PASS=secret 0 2 * * * /usr/bin/php script.php
Via a wrapper script
#!/bin/bash source /home/user/.env /usr/bin/php script.php
Call the wrapper from cron instead of the script directly.
Inside the script
Load the .env file explicitly at the top of the script using an absolute path - do not rely on the working directory.
# Python
load_dotenv('/home/user/project/.env')
# PHP
$dotenv->load('/var/www/html');
Step 7: Permissions
Script must be executable
ls -la /path/to/script.sh chmod +x /path/to/script.sh
Shell scripts need the execute bit set. PHP and Python scripts called via their interpreter (/usr/bin/php script.php) do not need it - but the interpreter binary itself must be executable by the cron user.
File ownership
ls -la /path/to/script.php # should be readable by the cron user: chown www-data:www-data /path/to/script.php
The cron user must be able to read the script and write to any output files or directories the script touches.
Step 8: Missing Trailing Newline
This is a surprisingly common cause on Debian and Ubuntu. Vixie-cron silently ignores the last line of a crontab if it does not end with a newline character.
When editing with crontab -e, always press Enter after your last entry before saving.
You can verify by running crontab -l | cat -A - the last line should end with $, not just the command text.
Step 9: Cron Expression Is Wrong
A valid-looking expression can still never fire. Common mistakes:
0 9 * * 1-5- correct (weekdays at 9 AM)9 0 * * 1-5- wrong (minute 9 of midnight, not 9 AM)* 9 * * 1-5- every minute between 9:00 and 9:59, not once at 9:00
- Quartz expression (6 fields) pasted into Linux crontab (5 fields)
- Day-of-month + day-of-week both set: Linux uses OR, not AND
- February 31st - logically valid syntax, never fires
Step 10: Timezone Mismatch
Cron runs in the server's system timezone. If you expect a job at 9 AM but the server is in UTC and you are in UTC+2, the job fires at 11 AM your time.
timedatectl # see current timezone cat /etc/timezone # on older systems
To schedule in a specific timezone on Linux, set the CRON_TZ variable at the top of the crontab:
CRON_TZ=Europe/Madrid 0 9 * * 1-5 /usr/bin/php /var/www/script.php
Platform-Specific Causes
Python / virtualenv
Using python3 instead of the virtualenv Python. The system Python does not have your project's packages. Always use /path/to/venv/bin/python.
Laravel scheduler
Missing the cd /project && prefix, wrong PHP path, or .env not readable by the cron user. Run php artisan schedule:list to verify schedules are registered.
Node.js / node-cron
In-process schedulers die when the Node process stops. If your app crashed or was redeployed, the cron jobs stop too. Use PM2 to keep the process alive.
Node.js cron job guideCentOS / RHEL: SELinux
SELinux can silently block cron jobs that access files or network resources outside their expected context. Check: sudo ausearch -m avc -ts recent | grep cron
Plesk / cPanel
In Plesk, "Run a command" tasks run in a chrooted environment - many system paths are inaccessible. Use "Run a PHP script" or "Fetch a URL" instead.
Plesk cron job guideGitHub Actions cron
GitHub schedules only run on the default branch, can be delayed 15+ minutes during peak load, and are automatically disabled after 60 days of repo inactivity. Forks do not run scheduled workflows by default.
GitHub Actions cron guideVercel cron jobs
Cron jobs only run on production deployments - not on preview. Changes to vercel.json only take effect after a new deployment. On the Hobby plan, jobs are limited to once per day maximum.
Cloudflare Workers cron
The scheduled() handler must use ctx.waitUntil() for async work - without it, async operations may be cut off. Triggers are registered on deploy; changes to wrangler.toml require a new wrangler deploy.
Windows / Task Scheduler
The task is set to "Run only when user is logged on" - change to "Run whether user is logged on or not". Also check that the "Start in" directory is set correctly.
Windows cron job guideHow to Check if a Cron Job Is Running
Before debugging why a cron job is not running, confirm whether it fired at all. These commands tell you definitively whether cron executed the job.
Check the cron log (Debian / Ubuntu)
grep CRON /var/log/syslog | grep "$(date +%b\ %e)"
Look for a line with CMD and your command. If it appears, cron ran the job.
If it does not appear, cron never fired it - the expression is wrong or cron is not running.
Check the cron log (CentOS / RHEL)
grep "$(date +%b\ %e)" /var/log/cron
Same logic. CMD in the log means the job fired. No CMD means cron did not run it.
Check with journald (any distro)
journalctl -u cron --since "1 hour ago" | grep CMD journalctl -u crond --since "1 hour ago" | grep CMD
Use cron on Debian/Ubuntu, crond on CentOS/RHEL.
Check via output log
tail -f /var/log/myjob.log
If you added >> /var/log/myjob.log 2>&1 to your cron entry, this file shows exactly what the script did and any errors it produced. No file means you have not enabled logging yet - see Step 3 above.
Check with a test entry
* * * * * echo "cron fired at $(date)" >> /tmp/cron-test.log
Add this to your crontab and wait 1-2 minutes. If /tmp/cron-test.log gets entries, cron is working. If not, the daemon is not running or the crontab is not being read.
Check via a timestamp file
# Add to the end of your script: date >> /tmp/myscript-lastrun.txt # Then check: cat /tmp/myscript-lastrun.txt
A simple way to verify the script itself completed - not just that cron fired it.
If the cron log shows CMD but this file is not updated, the script is crashing before it finishes.
Quick Diagnosis Checklist
- Is the cron daemon running? (
systemctl status cron) - Does the job appear in
crontab -l? - Does the crontab file end with a newline?
- Does the job appear in
/var/log/syslogor/var/log/cron? - Does the command work when run manually?
- Are all paths absolute (not relative)?
- Is the script executable? (
chmod +x) - Is output redirected to a log? (
>> log 2>&1) - Are environment variables set explicitly?
- Is the cron expression valid? (use the checker below)
Validate your cron expression
A wrong expression is the easiest problem to rule out. Paste it in the checker - it takes two seconds.