Skip to main content
All posts
· 5 min read

Why Your Laravel Cron Job Isn't Running (And How to Fix It)

Laravel cron jobs fail silently in more ways than you'd expect. Here's how to diagnose the actual cause — from misconfigured server cron entries to scheduler bugs to silent exit code failures.

Your Laravel scheduled command isn’t running. There’s nothing in the logs. The scheduler appears healthy. No alerts fired. You only noticed because a customer reported something was wrong — or because you checked manually.

This is the normal failure mode for Laravel cron jobs: silent.

Here’s how to find the actual cause.

Step 1: Confirm the server cron entry exists

Laravel’s scheduler only runs if the underlying cron entry is present and correct. Check:

crontab -l

You should see something like:

* * * * * cd /path/to/app && php artisan schedule:run >> /dev/null 2>&1

Common mistakes:

  • Wrong working directory (cd path)
  • Wrong PHP binary (php vs php8.2 vs a full path)
  • Output redirected to /dev/null before you can see errors
  • Entry is for the wrong user (root vs www-data vs deploy)

If the cron entry is missing or wrong, the scheduler never runs at all. This is the most common cause of “nothing is running” issues after a server rebuild or deployment to a new host.

Step 2: Verify the scheduler is actually running

Run it manually:

php artisan schedule:run

Look at the output. Laravel prints each task it runs and skips. If your command doesn’t appear, either its schedule expression doesn’t match the current time or it has an if() or skip() condition blocking it.

If the scheduler returns nothing and exits cleanly, it ran — it just had no tasks due at that minute.

Step 3: Check exit codes and output

A task can run and fail without Laravel treating it as a failure. Exit code behavior:

$schedule->command('reports:generate')
    ->daily()
    ->onFailure(function () {
        Log::error('reports:generate failed');
    });

Laravel’s scheduler captures exit codes from Artisan commands. If the command exits with a non-zero code and you haven’t set up a failure handler, it fails silently.

Check what exit code your command actually returns:

php artisan reports:generate; echo "Exit code: $?"

If it’s non-zero, the command is failing. That’s a different problem from the scheduler not running.

Step 4: Check for overlapping execution

If a scheduled task takes longer than its frequency, Laravel’s withoutOverlapping() will skip future runs while the previous one is still running:

$schedule->command('reports:generate')
    ->everyFiveMinutes()
    ->withoutOverlapping();

Without withoutOverlapping(), you get parallel runs. With it, you get silent skips. Check your queue for stuck jobs and look at whether a previous run is still active.

Step 5: Look at the logs

Laravel’s scheduler logs task runs if you use ->appendOutputTo() or ->emailOutputOnFailure(). Without these, failed tasks leave no trace.

Add output logging to diagnose intermittent failures:

$schedule->command('reports:generate')
    ->daily()
    ->appendOutputTo(storage_path('logs/scheduler.log'));

Then check that file after runs.

What persistent monitoring catches

Manual debugging finds the current problem. It doesn’t tell you:

  • How long the task has been failing
  • Which specific runs succeeded and which didn’t
  • Whether the scheduler ran at all on a given day

A monitoring layer that hooks into Laravel’s scheduler events (ScheduledTaskFinished, ScheduledTaskFailed) gives you per-run history with exit codes, durations, and exact timestamps. You see the first failure immediately rather than after a customer reports it.

Crontinel installs in two commands and starts recording that history immediately:

composer require crontinel/laravel
php artisan crontinel:install

No per-task configuration needed. It monitors every scheduled command automatically.

See also

use cases
horizon:purge Not Cleaning Jobs? Detect Silent Failures

horizon:purge can exit with code 0 while Redis memory keeps climbing. Here is exactly how to tell if it is actually working — and how to get alerted when it isn't.

blog
How to Detect Silent Cron Failures in Laravel

Laravel's scheduler runs your cron jobs but doesn't tell you when they fail. Here's how to detect silent failures before they become support tickets.

blog
Debugging Silent Laravel Cron Failures in Production

Debugging silent Laravel cron failures is harder than it sounds. The job never ran, the log is empty, and nobody got an alert. Here's how to find the real cause and make failures loud.

blog
How to Detect Laravel Queue Worker Stalls: When Workers Go Silent

A Laravel queue worker that's alive but not processing jobs is worse than a crashed worker — it gives no alert, no error, and no warning. Here's how to detect stalled workers, common causes, and how Crontinel catches silent failures before they compound.