Laravel Scheduler Not Running: Cron Setup and Fixes
Fix a Laravel scheduler that is not running by validating schedule definitions, cron user and paths, environment, locks, time zones, and server logs.
Prove the task is registered with schedule:list, run schedule:run as the cron user, then install one absolute-path cron entry and inspect locks, environment, and time-zone assumptions.
Laravel needs only one server-level cron entry: run php artisan schedule:run every minute and let the framework decide which tasks are due. When nothing happens, troubleshoot the schedule definition, the command, and the operating-system cron separately.
Confirm Laravel sees the task
From the production release directory, list scheduled events:
cd /var/www/example/current
php artisan schedule:list
Check that the command appears, its next run time is sensible, and the environment matches your expectation. Depending on the Laravel version and project structure, schedules are typically defined in routes/console.php or the console kernel.
Run the scheduler interactively:
php artisan schedule:run -vvv
This runs only tasks due at that moment. For a controlled test, temporarily schedule a harmless command every minute and write a unique marker to an application log. Remove the test after verification.
If Artisan itself fails, cron is not yet the problem. Fix the application boot error, missing dependency, file permission, or environment configuration first.
Run as the same user as cron
Commands that work in an SSH shell may fail under cron because cron has a smaller environment, a different working directory, and a different user.
Test explicitly:
sudo -u www-data /usr/bin/php \
/var/www/example/current/artisan schedule:run -vvv
The scheduler user needs read access to the release and write access to Laravel’s required runtime paths, usually storage and bootstrap/cache. It also needs access to the same cache, database, and external services used by the task.
Find the PHP path rather than assuming it:
command -v php
/usr/bin/php -v
A common failure is cron invoking a different PHP version from the interactive shell.
Install one correct cron entry
For a user crontab opened with crontab -e, use:
* * * * * cd /var/www/example/current && /usr/bin/php artisan schedule:run >> /dev/null 2>&1
For /etc/crontab or a file under /etc/cron.d/, include the user field:
* * * * * www-data cd /var/www/example/current && /usr/bin/php artisan schedule:run >> /dev/null 2>&1
Do not copy the second form into a user crontab: the extra username will be interpreted as part of the command.
During troubleshooting, send output to a dedicated log instead of /dev/null:
* * * * * cd /var/www/example/current && /usr/bin/php artisan schedule:run >> /var/log/example-scheduler.log 2>&1
Create the log with controlled ownership and rotation. Application commands can emit customer data, so do not retain raw output indefinitely.
Check the cron service and logs
On Ubuntu or Debian:
sudo systemctl status cron --no-pager
sudo journalctl -u cron --since "30 minutes ago"
On distributions using crond:
sudo systemctl status crond --no-pager
sudo journalctl -u crond --since "30 minutes ago"
Confirm the entry is installed for the intended user:
sudo crontab -u www-data -l
If your deployment replaces a current symlink, verify the cron command still resolves to the new release and that the new release has its dependencies and environment file.
Inspect environment and time zones
Cron may not inherit shell variables loaded by .profile. Laravel should obtain production configuration from its normal environment or .env, not from an interactive login shell.
If configuration is cached, rebuild it during deployment:
php artisan config:clear
php artisan config:cache
Time-zone bugs often look like a broken scheduler. Compare:
- The operating-system time zone.
- PHP’s configured time zone.
- Laravel’s application time zone.
- Any time zone specified on an individual scheduled event.
Prefer storing business instants in UTC and document which clock controls a recurring local-time task. Be cautious around daylight-saving transitions.
Resolve overlap and multi-server locks
A task using withoutOverlapping() acquires a cache lock. After an abnormal server termination, a stale lock may delay future runs until it expires. Inspect the task’s runtime and lock duration before clearing locks:
php artisan schedule:clear-cache
Use this command only when you have confirmed no valid instance is still running.
For multiple application servers, onOneServer() requires a shared supported cache. A local file cache on each host cannot coordinate the cluster. Give scheduled events stable names when Laravel requires them for shared locks.
If a task routinely runs longer than its frequency, do not merely disable overlap protection. Optimize it, split it into batches, or dispatch bounded jobs to a queue. Then use the Laravel queue and Supervisor guide to operate those workers safely.
Verify the complete path
Use an observable, harmless scheduled task and confirm:
schedule:listshows the correct next run.schedule:run -vvvexecutes it as the cron user.- The cron journal records an invocation every minute.
- The application records one successful task execution.
- Overlapping executions do not occur.
- A deployment does not point cron at an incomplete release.
Monitor the timestamp of important scheduled tasks. “Cron is running” is weaker than “the invoice task completed successfully within its expected window.”
Common mistakes and prevention
Frequent causes include using the wrong crontab syntax, relying on a relative PHP path, running cron as a user without write permission, caching an old environment, expecting schedule:run to execute a task that is not currently due, and using a non-shared cache with onOneServer().
Keep the single cron entry in infrastructure documentation, test the scheduler during deployment, and alert on missed business-level outcomes. See the zero-downtime Laravel deployment checklist for release coordination.
Frequently asked questions
Should I use schedule:work instead of cron in production?
schedule:work is useful as a foreground process in local development and some container designs. A conventional server normally uses one cron entry that invokes schedule:run every minute.
Does Supervisor replace the Laravel scheduler cron entry?
No. Supervisor commonly manages long-running queue workers. The scheduler still needs cron—or an intentionally supervised scheduler process—to trigger due tasks.
Official references
Have a question about this guide or an idea for a technical collaboration? Contact Bakry through the Dev Hub.
End of field note.