Linux Cron Job Not Running: Resolving Environment PATH and Shell Variable Issues
Troubleshoot why your Linux cron jobs aren't executing due to missing environment PATH variables, incorrect shell context, or daemon limitations.
Introduction
Have you ever set up a cron job only to find it never runs, or fails silently, despite working perfectly when executed manually from your terminal? This is a classic symptom of environment variable discrepancies between your interactive shell session and the minimal environment provided to cron jobs by the cron daemon. The most common culprit is a missing or incomplete PATH variable, preventing cron from locating necessary commands or scripts. This guide will walk you through diagnosing and resolving these elusive environment-related cron issues.
Symptom & Error Signature
The primary symptom is that a scheduled task simply doesn’t execute or complete its function, with no obvious error message appearing in your interactive terminal. When a cron job fails, it often doesn’t produce an error code directly visible to the user. Instead, you might observe:
- No output: The expected task (e.g., backup, log rotation, script execution) simply doesn’t happen.
- “Command not found” or “No such file or directory” in logs: If you’ve configured cron to email output or redirect to a log file, you might see errors like these for commands that are clearly available in your interactive shell.
Example of a failed cron attempt (often redirected to a log file or email):
/bin/sh: 1: my_custom_command: not found
OR
/bin/sh: 1: php: not found
Checking cron logs for execution attempts and potential errors:
grep CRON /var/log/syslog
Typical syslog output for a successful cron entry (often provides user and command):
Jun 27 10:00:01 hostname CRON[12345]: (www-data) CMD (/usr/bin/php /var/www/html/mysite/artisan schedule:run)
Typical syslog output for a problematic cron entry (daemon might execute, but script fails internally):
Jun 27 10:00:01 hostname CRON[12345]: (www-data) CMD (some_script.sh)
Jun 27 10:00:01 hostname CRON[12345]: (CRON) info (No MTA installed, discarding output)
In the second example, CRON successfully initiated some_script.sh, but the script itself likely failed and its output (which might contain the “command not found” error) was discarded because no Mail Transfer Agent (MTA) was configured.
Root Cause Analysis
The core of this problem lies in the fundamental difference between an interactive shell session and the environment provided by the cron daemon.
- Minimal Environment: When
cronexecutes a job, it does so in a highly simplified, non-interactive shell environment. This environment is intentionally sparse to ensure reproducibility and prevent unintended side effects from a user’s potentially complex shell configuration. - Restricted
PATHVariable: The most common environmental difference is thePATHvariable. In an interactive shell,PATHis extended by sourcing files like.bashrc,.profile,.zshrc, or system-wide configurations in/etc/profileor/etc/environment. These additions allow you to run commands likephp,npm,composer,docker, or custom scripts by their name without specifying their full path. Cron’s defaultPATHis typically very minimal, often limited to/usr/bin:/bin, and sometimes/usr/local/bin:/usr/sbin:/sbin. If your required command isn’t in these directories, cron won’t find it. - No Shell Initialization Files Sourced:
crondoes not execute~/.bashrc,~/.profile, or other shell initialization files for the user. This means any environment variables, aliases, or functions defined in these files (including modifications toPATH) are unavailable to the cron job. - Default Shell Differences: The default shell used by
cronis usually/bin/sh. On Debian and Ubuntu systems,/bin/shis symlinked todash(Debian Almquist Shell), which is a lightweight, POSIX-compliant shell. Your interactive shell is likelybashorzsh. While most basic commands are compatible, subtle differences in shell behavior or reliance onbash-specific features can cause scripts to fail underdash. - Missing Custom Environment Variables: Beyond
PATH, any other custom environment variables your script relies on (e.g.,APP_ENV,DATABASE_URL,JAVA_HOME) will also be missing unless explicitly defined for the cron job. - User Context: Cron jobs run under the user context specified in the crontab (
crontab -efor the current user, or/etc/cron.d/entries explicitly state the user). This user might have a differentPATHor environment compared to the user you used to test the script manually.
Step-by-Step Resolution
Here’s how to systematically troubleshoot and resolve cron job environment issues.
1. Use Absolute Paths for All Commands
The most robust and often simplest solution is to explicitly specify the full, absolute path to every command and script within your cron job.
Example:
Instead of:
* * * * * php /var/www/html/mysite/artisan schedule:run
(Which assumes php is in cron’s PATH)
Change to:
* * * * * /usr/bin/php /var/www/html/mysite/artisan schedule:run >> /var/log/mysite-cron.log 2>&1
How to find absolute paths:
Use which or command -v in your interactive shell:
which php
# Output: /usr/bin/php
which composer
# Output: /usr/local/bin/composer
which my_custom_script.sh
# Output: /home/myuser/bin/my_custom_script.sh
[!IMPORTANT] Always redirect
stdoutandstderrto a log file (>> /path/to/logfile.log 2>&1) for cron jobs. This is crucial for debugging, as it captures any output or errors the job might produce.
2. Define PATH Directly in the Crontab
If specifying absolute paths for every command becomes cumbersome, or if your script relies on many binaries not found in cron’s default PATH, you can define the PATH variable at the top of your crontab.
-
Find your full
PATH: In your interactive shell, run:echo $PATHThis will output a colon-separated list of directories.
-
Edit your crontab:
crontab -e -
Add
PATHat the top: Add aPATH=line at the very beginning of the file, before any cron job entries.PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin:/usr/games:/usr/local/games # Cron job entry * * * * * php /var/www/html/mysite/artisan schedule:run >> /var/log/mysite-cron.log 2>&1[!NOTE] While adding your full interactive
PATHcan resolve issues, it’s generally best practice to include only the necessary directories to keep the cron environment as minimal as possible.
3. Explicitly Set SHELL in the Crontab
If your script relies on specific features of bash (like arrays, advanced parameter expansion, or certain control structures) that might not be available in dash, you can force cron to use bash.
-
Edit your crontab:
crontab -e -
Add
SHELLat the top:SHELL=/bin/bash PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin * * * * * php /var/www/html/mysite/artisan schedule:run >> /var/log/mysite-cron.log 2>&1
4. Source Environment Files Within the Cron Job
For complex environment setups where many variables are needed, or if an application’s specific environment file needs to be loaded, you can source it directly within the cron command.
Example for a user’s ~/.profile or /etc/environment:
* * * * * /bin/bash -lc 'source ~/.profile && /usr/bin/php /var/www/html/mysite/artisan schedule:run' >> /var/log/mysite-cron.log 2>&1
bash -l: Starts a login shell, which often sources~/.profile(and sometimes~/.bash_profile).bash -c: Executes the string command.source ~/.profile: Explicitly sources the profile file. Adjust the path as needed (e.g.,source /etc/environmentfor system-wide variables).&&: Ensures the PHP command only runs if thesourcecommand is successful.
[!WARNING] Sourcing
~/.bashrcdirectly is generally discouraged for non-interactive scripts, as it can have unintended side effects and assume an interactive terminal.~/.profileor specific environment files (e.g.,/etc/environment) are usually more appropriate for setting environment variables for non-login, non-interactive shells.
5. Define Specific Environment Variables
If your script requires one or two specific environment variables (e.g., APP_ENV for a Laravel application, or DB_PASSWORD), you can define them directly in the crontab.
APP_ENV=production
DB_PASSWORD=my_secure_password
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
* * * * * /usr/bin/php /var/www/html/mysite/artisan schedule:run >> /var/log/mysite-cron.log 2>&1
Alternatively, you can pass them inline if the script is simple:
* * * * * APP_ENV=production /usr/bin/php /var/www/html/mysite/artisan schedule:run >> /var/log/mysite-cron.log 2>&1
6. Debugging with a Wrapper Script
For more complex scenarios, create a small wrapper shell script that sets up the environment, then executes your main script. This allows for more advanced debugging and modularity.
1. Create run_mysite_cron.sh:
#!/bin/bash
# run_mysite_cron.sh
# Set PATH explicitly
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin"
# Set other necessary environment variables
export APP_ENV="production"
# export DB_PASSWORD="my_secure_password" # Consider other secure ways for sensitive info
# Navigate to the script's directory if necessary
cd /var/www/html/mysite/ || { echo "Failed to change directory" >&2; exit 1; }
# Execute the actual command
/usr/bin/php artisan schedule:run "$@"
# Add logging for success/failure
if [ $? -eq 0 ]; then
echo "$(date): MySite cron job ran successfully."
else
echo "$(date): MySite cron job FAILED!" >&2
fi
2. Make the wrapper script executable:
chmod +x /path/to/run_mysite_cron.sh
3. Update crontab to call the wrapper:
* * * * * /path/to/run_mysite_cron.sh >> /var/log/mysite-cron.log 2>&1
This method centralizes environment setup and provides a single point of failure and debugging for the cron job.
7. Verify User Context
Ensure the cron job is being run by the correct user.
crontab -e: Edits the crontab for your current user./etc/crontabor/etc/cron.d/*: These system-wide crontabs require specifying the user that the command should run as (e.g.,www-data,root).
Example from /etc/cron.d/php on Ubuntu for FPM cleanup:
# /etc/cron.d/php
09,39 * * * * root [ -x /usr/lib/php/sessionclean ] && if [ ! -d /run/systemd/system ]; then /usr/lib/php/sessionclean; fi
Here, root is explicitly set as the user for the command. If you’re managing cron jobs for a web application, often www-data is the appropriate user.
[!WARNING] Avoid running cron jobs as
rootunless absolutely necessary, and only if the script is fully trusted. Use the principle of least privilege. If your application needs to write to web directories, run the cron job as the web server user (e.g.,www-dataon Debian/Ubuntu).
By systematically applying these steps, you should be able to diagnose and resolve environment-related issues preventing your Linux cron jobs from running successfully. Remember, robust logging is your best friend when dealing with background processes like cron.
