beginner 12 min read

How to Set Up Cron Jobs in Laravel (Task Scheduling Guide)

Learn how to set up cron jobs in Laravel using the built-in task scheduler. Step-by-step guide covering schedule definitions, testing, deployment, and debugging.

Prerequisites

  • Laravel 10+ (or Laravel 11+)
  • PHP 8.1+
  • Access to server or hosting with cron support
  • Composer installed

What Is Laravel Task Scheduling?

Laravel's task scheduler lets you define all your scheduled commands in a single place — your application code — instead of managing individual crontab entries on your server. You only need one system cron entry that runs every minute, and Laravel handles the rest.

This means your schedule is version-controlled, easy to test locally, and doesn't require SSH access to modify.

The scheduler supports a fluent API for defining when tasks run:

php
// Laravel 11+: routes/console.php
use Illuminate\Support\Facades\Schedule;

Schedule::command('emails:send')->dailyAt('08:00');
Schedule::command('reports:generate')->weeklyOn(1, '9:00');
Schedule::job(new CleanupOldRecords)->daily();

Under the hood, Laravel checks every minute which tasks are due and runs them.

Prerequisites

Before you start, make sure you have:

  • Laravel 10+ (or Laravel 11+ for the simplified console routing)
  • PHP 8.1+
  • A server where you can add a crontab entry (shared hosting, VPS, Docker, or a PaaS like Forge/Vapor)
  • Basic familiarity with Artisan commands

If you're using Laravel 11+, schedules are defined in routes/console.php. For Laravel 10 and earlier, you'll use the schedule method in app/Console/Kernel.php.

Quick Start: Your First Scheduled Task

Step 1: Define a scheduled task

For Laravel 11+, open routes/console.php:

php
use Illuminate\Support\Facades\Schedule;

Schedule::command('inspire')->hourly();

For Laravel 10 and earlier, open app/Console/Kernel.php:

php
protected function schedule(Schedule $schedule): void
{
    $schedule->command('inspire')->hourly();
}

Step 2: Add the system cron entry

On your server, run crontab -e and add this single line:

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

This tells cron to run Laravel's scheduler every minute. This might seem excessive, but it's by design — Laravel's scheduler manages timing internally. Each minute, schedule:run checks which tasks are actually due based on your fluent schedule definitions (->hourly(), ->dailyAt('09:00'), etc.) and only executes those. The every-minute cron is just the heartbeat that lets Laravel's scheduler do its job.

Step 3: Verify it works

bash
php artisan schedule:list

This shows all registered scheduled tasks and when they'll next run.

Scheduling Methods Deep Dive

Laravel provides a rich API for scheduling. Here are the most common methods:

Frequency methods:

  • ->cron('* * * * *') — raw cron expression
  • ->everyMinute() — run every minute
  • ->everyFiveMinutes() — run every 5 minutes
  • ->hourly() — run at the start of every hour
  • ->hourlyAt(15) — run at minute 15 of every hour
  • ->daily() — run daily at midnight
  • ->dailyAt('13:00') — run daily at 1 PM
  • ->weekly() — run weekly on Sunday at midnight
  • ->weeklyOn(1, '8:00') — run weekly on Monday at 8 AM
  • ->monthly() — run on the first of each month
  • ->quarterly() — run on the first day of each quarter
  • ->yearly() — run on January 1st

Constraint methods:

php
Schedule::command('report:daily')
    ->daily()
    ->weekdays()
    ->between('8:00', '17:00')
    ->when(fn () => config('app.env') === 'production')
    ->skip(fn () => app()->isDownForMaintenance());

Overlap prevention:

php
Schedule::command('analytics:process')
    ->hourly()
    ->withoutOverlapping()
    ->onOneServer()
    ->runInBackground();

Common Laravel Cron Expressions

When using the ->cron() method directly, here are common expressions:

  • ->cron('*/5 * * * *') — Every 5 minutes
  • ->cron('0 * * * *') — Every hour
  • ->cron('0 0 * * *') — Daily at midnight
  • ->cron('0 9 * * 1-5') — Weekdays at 9 AM
  • ->cron('0 0 1 * *') — First day of every month
  • ->cron('0 0 * * 0') — Every Sunday at midnight

Most developers prefer the fluent API (->daily(), ->hourly(), etc.) since it's more readable, but the ->cron() method is useful for non-standard schedules.

Every weekday at 9:00 AM

Next runs (UTC):

Mon, May 18, 2026 09:00

Tue, May 19, 2026 09:00

Wed, May 20, 2026 09:00

Testing Schedules Locally

Laravel provides several commands for testing your schedules without waiting for the actual cron to fire:

List all scheduled tasks:

bash
php artisan schedule:list

Shows each task, its frequency, next due date, and description.

Run the scheduler once:

bash
php artisan schedule:run

Executes any tasks that are currently due. Useful for one-off testing.

Run the scheduler continuously (local dev):

bash
php artisan schedule:work

Runs the scheduler in the foreground, checking every minute. Perfect for local development — no need to set up a real crontab.

Test a specific task:

bash
php artisan schedule:test

Shows a list of scheduled tasks and lets you pick one to run immediately, regardless of its schedule.

Deployment: Setting Up the System Cron

The exact setup varies by hosting environment:

Ubuntu / Debian:

bash
crontab -e
# Add this line:
* * * * * cd /var/www/html && php artisan schedule:run >> /dev/null 2>&1

Docker:

In your Dockerfile or entrypoint:

dockerfile
RUN echo "* * * * * cd /var/www/html && php artisan schedule:run >> /proc/1/fd/1 2>&1" | crontab -

Laravel Forge:

Forge automatically adds the scheduler cron entry when you deploy a Laravel app. No manual setup needed.

Laravel Vapor (serverless):

Vapor uses AWS CloudWatch Events to trigger the scheduler. Define it in your vapor.yml:

yaml
environments:
  production:
    scheduler: true

Debugging: "Why Is My Laravel Schedule Not Running?"

Common causes and fixes:

1. The system cron entry is missing or wrong

Verify with crontab -l. The path to your project and PHP binary must be correct. Use absolute paths:

bash
* * * * * /usr/bin/php /var/www/html/artisan schedule:run >> /dev/null 2>&1

2. The schedule is defined but not registering

Run php artisan schedule:list. If your task isn't listed, check that you're defining it in the right file (routes/console.php for Laravel 11+, Kernel.php for Laravel 10).

3. Timezone mismatch

Laravel defaults to UTC. Set your app timezone in config/app.php:

php
'timezone' => 'America/New_York',

Or set it per-task:

php
Schedule::command('report:daily')
    ->dailyAt('09:00')
    ->timezone('America/New_York');

4. Overlapping prevention is blocking runs

If you use ->withoutOverlapping(), a stuck or long-running task can prevent future runs. Check for stale mutex files in storage/framework/schedule-*.

5. Output is being swallowed

Log output to a file to see errors:

php
Schedule::command('emails:send')
    ->daily()
    ->appendOutputTo(storage_path('logs/schedule.log'));

Production Tips

Logging and monitoring:

php
Schedule::command('billing:charge')
    ->daily()
    ->onSuccess(fn () => Log::info('Billing completed'))
    ->onFailure(fn () => Log::error('Billing failed'))
    ->emailOutputOnFailure('ops@example.com');

Health checks:

Use a monitoring service like Oh Dear or Healthchecks.io:

php
Schedule::command('important:task')
    ->hourly()
    ->pingOnSuccess('https://hc-ping.com/your-uuid')
    ->pingOnFailure('https://hc-ping.com/your-uuid/fail');

Maintenance mode:

By default, scheduled tasks don't run during maintenance mode. To override:

php
Schedule::command('heartbeat:ping')->everyMinute()->evenInMaintenanceMode();

Multi-server deployments:

Use ->onOneServer() with a cache driver that supports locks (Redis, Memcached, DynamoDB) to prevent duplicate runs across servers.

Frequently Asked Questions