Laravel cron job setup for the scheduler

Laravel needs one crontab line running php artisan schedule:run every minute. How to add it on a server, Forge, Ploi or Docker, and why it may not run.

Updated View as Markdown

On this page

Laravel needs exactly one cron job: a crontab line that runs php artisan schedule:run every minute. Each run checks the tasks in routes/console.php and starts the ones due at that minute; you never add a cron line per task.

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

Everything below was run on Laravel 13.42 and checked against the Laravel 13.x scheduling docs on 10 Nov 2026. It applies to Laravel 11 and 12 too.

Add the crontab line on a server

  1. Log in as the user that owns the app and runs PHP-FPM, often www-data, forge or ploi. Not root: files the scheduler creates, such as the day's log file, would belong to root and the web server couldn't write to them.

  2. Find the PHP binary and the project path:

    Terminal
    which php
    pwd
    
  3. Open the crontab with crontab -e and add one line, with absolute paths:

    * * * * * cd /var/www/example.com && /usr/bin/php artisan schedule:run >> /dev/null 2>&1
    

    Replace /var/www/example.com with your project directory and /usr/bin/php with the output of which php. Cron runs with a minimal PATH, so a plain php can find another PHP version than your shell does, or none.

  4. Check it was saved: crontab -l. For another user: sudo crontab -u www-data -l.

The five * mean every minute of every hour of every day. The server's timezone doesn't matter for this line, because it runs every minute anyway; task times come from Laravel (see timezones below).

>> /dev/null 2>&1 throws away the output. While you set things up, send it to a file instead, so a broken run leaves a trace:

* * * * * cd /var/www/example.com && /usr/bin/php artisan schedule:run >> /var/www/example.com/storage/logs/schedule.log 2>&1

Check that it runs

schedule:list shows every task with its cron expression and next run:

Terminal
php artisan schedule:list
 * * * * * php artisan reports:send .. Next Due: 45 seconds from now
 30 2 * * * php artisan backup:run .. Next Due: 9 hours from now

schedule:run by hand shows what it starts (this is a Mac with Homebrew PHP). The scheduler runs each task with the same PHP binary as schedule:run itself, which the second line shows:

 2026-11-10 16:45:14 Running ['artisan' reports:send] .. 455.41ms DONE
 ⇂ '/opt/homebrew/Cellar/php/8.5.10/bin/php' 'artisan' reports:send > '/dev/null' 2>&1

php artisan schedule:test lets you pick one task and run it now, whatever its schedule.

Laravel Forge, Ploi and Laravel Cloud

  • Laravel Forge: turn on the Laravel Scheduler toggle of the site. Forge creates the every-minute job with the site's PHP version (Forge docs). Jobs you add yourself live under Processes → Scheduler of the server or site.
  • Ploi: on the server, open Cron jobs and add a job with the command php8.5 /home/ploi/example.com/artisan schedule:run, user ploi, frequency every minute (Ploi docs). Use your site's PHP version and path.
  • Laravel Cloud: click the environment's App cluster, turn on the Scheduler toggle, then save and redeploy. There is no crontab line to write. With more than one replica, use onOneServer() or tasks run on every replica (Cloud docs).

The three docs pages were read on 10 Nov 2026.

Docker and Kubernetes

Containers usually have no cron daemon. Two common setups:

  • A scheduler container running schedule:work. Same image and environment as the app, with php artisan schedule:work as its command. It stays in the foreground and calls the scheduler every minute. Run exactly one of these, or tasks run once per container.

    YAML
    # docker-compose.yml
    scheduler:
      image: your-app-image
      command: php artisan schedule:work
      env_file: .env
      restart: unless-stopped
    
  • A Kubernetes CronJob with schedule * * * * * and command php artisan schedule:run. Each minute starts a new pod; set concurrencyPolicy: Forbid so slow runs don't pile up.

The Laravel docs present schedule:work for local development. In a container it does the same job as cron, with one difference: when the process dies, nothing runs until your orchestrator restarts it.

Tasks that run longer, or on several servers

Three options decide what happens when the cron line fires again before a task is done, or fires on more than one server:

PHP
use Illuminate\Support\Facades\Schedule;

Schedule::command('reports:send')->everyFiveMinutes()->withoutOverlapping(10);
Schedule::command('invoices:sync')->hourly()->onOneServer();
Schedule::command('analytics:report')->daily()->runInBackground();
  • withoutOverlapping() skips a run while the previous one still holds a cache lock. The lock expires after 24 hours by default; pass minutes, as above, so a killed process doesn't block the task for a day.
  • onOneServer() lets only the first server that grabs the lock run the task. It needs a database, memcached, dynamodb or redis default cache that all servers share. Scheduled closures also need a ->name('…').
  • runInBackground() starts a command or exec task without waiting for it. Tasks due at the same minute otherwise run one after another, so one slow task delays the rest.

Sub-minute tasks (everyTenSeconds() and the like) keep schedule:run alive for the whole minute. Add php artisan schedule:interrupt to your deploy script, or the old run keeps using the old code until the minute ends.

Timezones

Laravel decides when a task is due in the app's timezone, timezone in config/app.php (UTC by default). Set another one per task, or for all tasks with schedule_timezone:

PHP
// config/app.php
'timezone' => 'UTC',
'schedule_timezone' => 'Europe/Amsterdam',
PHP
Schedule::command('backup:run')->dailyAt('02:30')->timezone('Europe/Amsterdam');

On the nights the clocks change, a task in a daylight saving timezone can run twice or not at all; the Laravel docs advise against timezone scheduling for that reason. Keep the app in UTC, and don't put important tasks between 02:00 and 03:00 local time.

Why the scheduler isn't running

Most "the scheduler stopped" reports come down to one of these:

SymptomLikely causeCheck
Nothing runs, no errors anywhereNo crontab line on this server, or it's in another user's crontabcrontab -l as the app user
Works by hand, not from cronCron finds another php, or the cd path is wrongUse absolute paths; log output to a file instead of /dev/null
Ran until the last deployNew server or container without cron; deploy left the app in maintenance modephp artisan schedule:list on the server; tasks skip php artisan down unless they have evenInMaintenanceMode()
One task never runsA stuck withoutOverlapping lock after a crashphp artisan schedule:clear-cache
A task runs on every serveronOneServer() without a shared cacheYour default cache store
Runs at the wrong hourApp timezone or daylight saving timephp artisan schedule:list
Nothing runs since someone paused itphp artisan schedule:pausephp artisan schedule:continue

The scheduler monitoring guide reproduces the stuck lock and the "it failed but said DONE" case, and shows how to catch each.

Know when it stops

A crontab line can disappear with a server move and nothing complains: no task runs, so no task fails. The usual fix is a heartbeat per task: ->pingOnSuccess($url) to a cron monitoring service, which alerts when a ping is missing.

We make Lampwick, which checks the schedule without heartbeats: the Laravel package reports every scheduled task run, and Schedule health learns each task's cron and timezone from its runs. When a task misses its run you get an alert, and when no task runs at all you get one "scheduler not running" alert for the project instead of one per task. Details are in the Schedule health docs and on the cron monitoring page.

Next step

Add the crontab line, run php artisan schedule:list, then set up monitoring for the scheduler so you hear about the night it doesn't run.