決まった時刻に動かす(タスクスケジュール)
「毎日0時に動かす」「5分ごとに動かす」といった決まった時刻の仕事を、Laravel のタスクスケジュールで書く方法と、頻度・制限・出力・フックの一覧を説明します。
タスクスケジュールは、「毎日夜中の0時にこの仕事をする」のように、決まった時刻に自動で動かす仕事を決めるしくみです。目覚まし時計に、予定を書いたメモをつけておくようなものです。昔は、サーバーの cron(決まった時刻にコマンドを動かす、サーバーの仕組み)に、仕事ごとに1行ずつ書いていました。この方法は、予定がソースコードの管理(Git など)の外に出てしまい、見たり足したりするたびにサーバーへログインする手間がかかります。Laravel のスケジューラーなら、予定をアプリの中のコードで書けます。サーバーに書く cron は、たった1行で済みます。予定は、ふつう routes/console.php に書きます。
スケジュールを書く#
予定は、アプリの routes/console.php にまとめて書けます。次の例は、毎日0時に、データベースの表の中身を消すクロージャ(名前のない関数)を動かします。
<?php
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schedule;
Schedule::call(function () {
DB::table('recent_users')->delete();
})->daily();
クロージャのほかに、__invoke メソッドを持つ PHP のクラス(関数のように呼び出せるクラス)も動かせます。
Schedule::call(new DeleteRecentUsers)->daily();
routes/console.php をコマンドの定義だけに使いたいときは、bootstrap/app.php の withSchedule に書きます。スケジューラーを受け取るクロージャを渡します。
use Illuminate\Console\Scheduling\Schedule;
->withSchedule(function (Schedule $schedule) {
$schedule->call(new DeleteRecentUsers)->daily();
})
予定の一覧と、次に動く時刻は、schedule:list コマンドで見られます。
php artisan schedule:list
Artisan コマンドを動かす#
クロージャのほかに、Artisan コマンドやシステムのコマンドも動かせます。command メソッドに、コマンドの名前かクラスを渡します。
クラスの名前で渡すときは、コマンドに渡す引数を配列で渡せます。
use App\Console\Commands\SendEmailsCommand;
use Illuminate\Support\Facades\Schedule;
Schedule::command('emails:send Taylor --force')->daily();
Schedule::command(SendEmailsCommand::class, ['Taylor', '--force'])->daily();
クロージャで作った Artisan コマンドを動かす#
クロージャで定義した Artisan コマンドは、定義のあとに、スケジュールのメソッドをつなげられます。
Artisan::command('delete:recent-users', function () {
DB::table('recent_users')->delete();
})->purpose('Delete recent users')->daily();
クロージャのコマンドに引数を渡すときは、schedule メソッドに渡します。
Artisan::command('emails:send {user} {--force}', function ($user) {
// ...
})->purpose('Send emails to the specified user')->schedule(['Taylor', '--force'])->daily();
キューのジョブを動かす#
job メソッドで、キューのジョブを動かせます。call でクロージャを作って、その中でジョブを送らなくても済みます。
use App\Jobs\Heartbeat;
use Illuminate\Support\Facades\Schedule;
Schedule::job(new Heartbeat)->everyFiveMinutes();
第2引数にキューの名前、第3引数に接続の名前を渡して、送り先を決められます。
use App\Jobs\Heartbeat;
use Illuminate\Support\Facades\Schedule;
// Dispatch the job to the "heartbeats" queue on the "sqs" connection...
Schedule::job(new Heartbeat, 'heartbeats', 'sqs')->everyFiveMinutes();
シェルのコマンドを動かす#
exec メソッドで、OS のコマンドを動かせます。
use Illuminate\Support\Facades\Schedule;
Schedule::exec('node /home/forge/script.js')->daily();
動かす頻度の一覧#
ここまでで、決まった間隔で動かす例をいくつか見ました。仕事に決められる頻度は、ほかにもたくさんあります。下の表では、メソッドの -> と最後の ; を省いて書いています。
| メソッド | 説明 |
|---|---|
cron('* * * * *') |
自分で書いた cron の形式の時刻で動かす |
everySecond() |
1秒ごと |
everyTwoSeconds() |
2秒ごと |
everyFiveSeconds() |
5秒ごと |
everyTenSeconds() |
10秒ごと |
everyFifteenSeconds() |
15秒ごと |
everyTwentySeconds() |
20秒ごと |
everyThirtySeconds() |
30秒ごと |
everyMinute() |
1分ごと |
everyTwoMinutes() |
2分ごと |
everyThreeMinutes() |
3分ごと |
everyFourMinutes() |
4分ごと |
everyFiveMinutes() |
5分ごと |
everyTenMinutes() |
10分ごと |
everyFifteenMinutes() |
15分ごと |
everyThirtyMinutes() |
30分ごと |
hourly() |
1時間ごと |
hourlyAt(17) |
1時間ごと、毎時17分に |
everyOddHour($minutes = 0) |
奇数の時刻(1時、3時…)ごと |
everyTwoHours($minutes = 0) |
2時間ごと |
everyThreeHours($minutes = 0) |
3時間ごと |
everyFourHours($minutes = 0) |
4時間ごと |
everySixHours($minutes = 0) |
6時間ごと |
daily() |
毎日0時に |
dailyAt('13:00') |
毎日13時に |
twiceDaily(1, 13) |
毎日1時と13時に |
twiceDailyAt(1, 13, 15) |
毎日1時15分と13時15分に |
daysOfMonth([1, 10, 20]) |
月の決まった日(この例は1日・10日・20日)に |
weekly() |
毎週日曜の0時に |
weeklyOn(1, '8:00') |
毎週月曜の8時に |
monthly() |
毎月1日の0時に |
monthlyOn(4, '15:00') |
毎月4日の15時に |
twiceMonthly(1, 16, '13:00') |
毎月1日と16日の13時に |
lastDayOfMonth('15:00') |
毎月の最後の日の15時に |
quarterly() |
四半期(3か月)ごとの最初の日の0時に |
quarterlyOn(4, '14:00') |
四半期ごとの4日の14時に |
yearly() |
毎年1月1日の0時に |
yearlyOn(6, 1, '17:00') |
毎年6月1日の17時に |
timezone('America/New_York') |
その仕事のタイムゾーンを決める |
これらのメソッドに、条件をつなげると、曜日などを絞った細かい予定が作れます。たとえば、毎週月曜に動かす予定です。
use Illuminate\Support\Facades\Schedule;
// Run once per week on Monday at 1 PM...
Schedule::call(function () {
// ...
})->weekly()->mondays()->at('13:00');
// Run hourly from 8 AM to 5 PM on weekdays...
Schedule::command('foo')
->weekdays()
->hourly()
->timezone('America/Chicago')
->between('8:00', '17:00');
絞り込みに使える条件は、次のとおりです。
| メソッド | 説明 |
|---|---|
weekdays() |
平日だけ |
weekends() |
週末だけ |
sundays() |
日曜だけ |
mondays() |
月曜だけ |
tuesdays() |
火曜だけ |
wednesdays() |
水曜だけ |
thursdays() |
木曜だけ |
fridays() |
金曜だけ |
saturdays() |
土曜だけ |
days(array|mixed) |
決めた曜日だけ |
between($startTime, $endTime) |
開始と終了の時刻のあいだだけ |
unlessBetween($startTime, $endTime) |
開始と終了の時刻のあいだは動かさない |
when(Closure) |
クロージャが true を返すときだけ |
environments($env) |
決めた環境(手元・本番など)だけ |
曜日で絞る#
days で、仕事を動かす曜日を絞れます。たとえば、日曜と水曜だけ、1時間ごとに動かします(曜日は、0が日曜から数えた番号です)。
use Illuminate\Support\Facades\Schedule;
Schedule::command('emails:send')
->hourly()
->days([0, 3]);
Illuminate\Console\Scheduling\Schedule のクラスにある定数でも、曜日を書けます。
use Illuminate\Support\Facades;
use Illuminate\Console\Scheduling\Schedule;
Facades\Schedule::command('emails:send')
->hourly()
->days([Schedule::SUNDAY, Schedule::WEDNESDAY]);
時間帯で絞る#
between で、1日のうちの時間帯を絞れます。
Schedule::command('emails:send')
->hourly()
->between('7:00', '22:00');
逆に、unlessBetween は、決めた時間帯だけ動かしません。
Schedule::command('emails:send')
->hourly()
->unlessBetween('23:00', '4:00');
条件で絞る(true か false か)#
when に渡したクロージャが true を返したときだけ、仕事が動きます(ほかの条件が止めていなければ)。
Schedule::command('emails:send')->daily()->when(function () {
return true;
});
skip は、when の逆です。クロージャが true を返すと、その仕事は動きません。
Schedule::command('emails:send')->daily()->skip(function () {
return true;
});
when を何回かつなげたときは、全部が true のときだけ、仕事が動きます。
環境で絞る#
environments で、決めた環境でだけ仕事を動かせます。環境は、APP_ENV という環境変数(環境ごとに変える設定値)で決まります。
Schedule::command('emails:send')
->daily()
->environments(['staging', 'production']);
タイムゾーン#
timezone で、予定の時刻を、決めたタイムゾーン(地域ごとの標準の時刻)で読むように決められます。
use Illuminate\Support\Facades\Schedule;
Schedule::command('report:generate')
->timezone('America/New_York')
->at('2:00');
全部の予定に同じタイムゾーンをつけたいときは、アプリの app 設定に、schedule_timezone を書きます。
'timezone' => 'UTC',
'schedule_timezone' => 'America/Chicago',
注意
タイムゾーンによっては、夏時間(季節で時計を1時間ずらすしくみ)があります。夏時間が切り替わるとき、仕事が2回動いたり、まったく動かなかったりすることがあります。そのため、タイムゾーンをつけた予定は、できるだけ使わないほうが安全です。
仕事が重ならないようにする#
既定では、前の仕事がまだ動いていても、次の時刻が来れば、同じ仕事が動きます。それを防ぐには、withoutOverlapping を使います。
use Illuminate\Support\Facades\Schedule;
Schedule::command('emails:send')->withoutOverlapping();
この例の emails:send(Artisan コマンド)は、毎分動かしますが、まだ前のが動いているときは動きません。かかる時間が大きく変わる仕事で、何分かかるか読めないときに便利です。
必要なら、「重ならない」ためのロックの期限を、分で決められます。既定では24時間で外れます。
Schedule::command('emails:send')->withoutOverlapping(10);
withoutOverlapping は、内部で、アプリのキャッシュを使ってロック(同時に1つだけが使える鍵)を取ります。必要なら、schedule:clear-cache コマンドで、そのロックを消せます。ふつうは、サーバーの思わぬ問題で、仕事が止まったままになったときだけ使います。
1台のサーバーだけで動かす#
注意
この機能を使うには、アプリの既定のキャッシュが database・memcached・dynamodb・redis のどれかである必要があります。また、すべてのサーバーが、同じキャッシュのサーバーにつながっていなければなりません。
スケジューラーを複数のサーバーで動かしているとき、ある仕事を、1台のサーバーだけで動かすようにできます。たとえば、毎週金曜の夜にレポートを作る仕事があるとします。3台のサーバーでスケジューラーが動いていれば、3台全部で仕事が動き、レポートが3つできてしまいます。これは困ります。
1台だけで動かすには、仕事を定義するときに onOneServer を使います。仕事を最初に取ったサーバーが、アトミックロック(途中で割り込まれない鍵)を取り、ほかのサーバーが同じ仕事を同時に動かさないようにします。
use Illuminate\Support\Facades\Schedule;
Schedule::command('report:generate')
->fridays()
->at('17:00')
->onOneServer();
useCache で、このロックに使うキャッシュを選べます。
Schedule::useCache('database');
1台のサーバーで動かす仕事に名前をつける#
同じジョブを、ちがう引数で何回か動かしながら、それぞれを1台のサーバーだけで動かしたいことがあります。そのときは、name で、予定ごとに別の名前をつけます。
Schedule::job(new CheckUptime('https://laravel.com'))
->name('check_uptime:laravel.com')
->everyFiveMinutes()
->onOneServer();
Schedule::job(new CheckUptime('https://vapor.laravel.com'))
->name('check_uptime:vapor.laravel.com')
->everyFiveMinutes()
->onOneServer();
クロージャの予定を1台のサーバーだけで動かすときも、名前をつける必要があります。
Schedule::call(fn () => User::resetApiRequestCount())
->name('reset-api-request-count')
->daily()
->onOneServer();
裏で動かす#
既定では、同じ時刻に動く複数の仕事は、schedule に書いた順に、1つずつ動きます。長くかかる仕事があると、あとの仕事の開始が、思ったよりずっと遅れることがあります。全部を同時に動かしたいときは、runInBackground で、裏で動かします。
use Illuminate\Support\Facades\Schedule;
Schedule::command('analytics:report')
->daily()
->runInBackground();
注意
runInBackground が使えるのは、command と exec で作った予定だけです。
メンテナンスモード#
アプリがメンテナンスモードのあいだは、予定の仕事は動きません。サーバーでやっている保守の作業のじゃまをしないためです。メンテナンスモードでも動かしたい仕事には、evenInMaintenanceMode を呼びます。
Schedule::command('emails:send')->evenInMaintenanceMode();
予定の仕事を一時停止する#
デプロイ済みのコードを変えずに、予定の仕事の処理を、一時的に止められます。schedule:pause コマンドを使います。
php artisan schedule:pause
止めているあいだは、予定の仕事は何も動きません。再開するには、schedule:continue を使います。
php artisan schedule:continue
止めているあいだも動かしたい仕事には、evenWhenPaused をつけます。
Schedule::command('emails:send')->evenWhenPaused();
予定をグループにする#
似た設定の仕事がいくつもあるときは、グループにまとめると、同じ設定を何度も書かずに済みます。コードがすっきりし、関連する仕事の設定もそろいます。
グループを作るには、先に共通の設定のメソッドを呼び、最後に group を呼びます。group に渡したクロージャの中で、その設定を共有する仕事を書きます。
use Illuminate\Support\Facades\Schedule;
Schedule::daily()
->onOneServer()
->timezone('America/New_York')
->group(function () {
Schedule::command('emails:send --force');
Schedule::command('emails:prune');
});
スケジューラーを動かす#
予定の書き方が分かったので、実際にサーバーで動かす方法を見てみましょう。schedule:run コマンドは、すべての予定を調べて、サーバーの今の時刻で動かす必要があるかを判断します。
そのため、Laravel のスケジューラーを使うときは、サーバーに、schedule:run を毎分動かす cron の設定を1行だけ足します。cron の足し方が分からなければ、Laravel Cloud のような、予定の実行を任せられるサービスもあります。
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
1分より短い間隔の仕事#
ほとんどの OS では、cron は最短で1分に1回しか動かせません。しかし、Laravel のスケジューラーなら、1秒に1回まで、短い間隔で動かせます。
use Illuminate\Support\Facades\Schedule;
Schedule::call(function () {
DB::table('recent_users')->delete();
})->everySecond();
1分より短い間隔の仕事があると、schedule:run は、すぐには終わらず、その分の終わりまで動き続けます。そのあいだに、必要な短い間隔の仕事を、すべて動かします。
短い間隔の仕事が、思ったより長くかかると、あとの仕事が遅れます。そのため、短い間隔の仕事は、実際の処理をキューのジョブや裏のコマンドに任せるのがおすすめです。
use App\Jobs\DeleteRecentUsers;
Schedule::job(new DeleteRecentUsers)->everyTenSeconds();
Schedule::command('users:delete')->everyTenSeconds()->runInBackground();
短い間隔の仕事を中断する#
短い間隔の仕事があると、schedule:run は、動き出した分のあいだずっと動きます。そのため、デプロイのときに、中断したいことがあります。そうしないと、すでに動いている schedule:run が、その分が終わるまで、前のデプロイのコードを使い続けます。
動いている schedule:run を中断するには、デプロイの手順に schedule:interrupt を足します。デプロイが終わったあとに動かします。
php artisan schedule:interrupt
手元でスケジューラーを動かす#
手元の開発用のパソコンには、ふつう、スケジューラーの cron は足しません。かわりに、schedule:work を使います。このコマンドはターミナルの画面に出たまま動き続け、止めるまで、毎分スケジューラーを呼び出します。1分より短い間隔の仕事があれば、その分のあいだも動き続けて、処理します。
php artisan schedule:work
仕事の出力#
スケジューラーには、仕事の出力を扱う便利なメソッドがあります。まず、sendOutputTo で、出力をファイルに送り、あとで見られます。
use Illuminate\Support\Facades\Schedule;
Schedule::command('emails:send')
->daily()
->sendOutputTo($filePath);
ファイルの終わりに足していきたいときは、appendOutputTo を使います。
Schedule::command('emails:send')
->daily()
->appendOutputTo($filePath);
emailOutputTo で、出力を好きなメールアドレスに送れます。送る前に、Laravel のメールの設定をしておいてください。
Schedule::command('report:generate')
->daily()
->sendOutputTo($filePath)
->emailOutputTo('taylor@example.com');
仕事のコマンドが、0以外の終了コード(失敗を表す数)で終わったときだけ、出力をメールしたいなら、emailOutputOnFailure を使います。
Schedule::command('report:generate')
->daily()
->emailOutputOnFailure('taylor@example.com');
注意
emailOutputTo・emailOutputOnFailure・sendOutputTo・appendOutputTo が使えるのは、command と exec で作った予定だけです。
| メソッド | 説明 |
|---|---|
sendOutputTo |
出力をファイルに送る |
appendOutputTo |
出力をファイルの終わりに足す |
emailOutputTo |
出力をメールで送る |
emailOutputOnFailure |
失敗したときだけ、出力をメールで送る |
仕事の前後に動かす処理(フック)#
before と after で、仕事の前と後に動かす処理を決められます。
use Illuminate\Support\Facades\Schedule;
Schedule::command('emails:send')
->daily()
->before(function () {
// The task is about to execute...
})
->after(function () {
// The task has executed...
});
onSuccess と onFailure は、仕事が成功したとき・失敗したときに動かす処理を決めます。失敗とは、予定の Artisan コマンドやシステムのコマンドが、0以外の終了コードで終わったことです。
Schedule::command('emails:send')
->daily()
->onSuccess(function () {
// The task succeeded...
})
->onFailure(function () {
// The task failed...
});
コマンドの出力があるなら、after・onSuccess・onFailure のクロージャで、引数 $output に Illuminate\Support\Stringable の型を書くと、その出力を受け取れます。
use Illuminate\Support\Stringable;
Schedule::command('emails:send')
->daily()
->onSuccess(function (Stringable $output) {
// The task succeeded...
})
->onFailure(function (Stringable $output) {
// The task failed...
});
URL に知らせる(ping)#
pingBefore と thenPing で、仕事の前や後に、決めた URL へ自動で知らせを送れます(ping)。外のサービス(たとえば Envoyer)に、予定の仕事が始まったことや終わったことを知らせたいときに便利です。
Schedule::command('emails:send')
->daily()
->pingBefore($url)
->thenPing($url);
pingOnSuccess と pingOnFailure は、仕事が成功したとき・失敗したときだけ、決めた URL へ知らせます。失敗とは、0以外の終了コードで終わったことです。
Schedule::command('emails:send')
->daily()
->pingOnSuccess($successUrl)
->pingOnFailure($failureUrl);
pingBeforeIf・thenPingIf・pingOnSuccessIf・pingOnFailureIf は、決めた条件が true のときだけ、URL へ知らせます。
Schedule::command('emails:send')
->daily()
->pingBeforeIf($condition, $url)
->thenPingIf($condition, $url);
Schedule::command('emails:send')
->daily()
->pingOnSuccessIf($condition, $successUrl)
->pingOnFailureIf($condition, $failureUrl);
| メソッド | 説明 |
|---|---|
before |
仕事の前に処理を動かす |
after |
仕事の後に処理を動かす |
onSuccess |
成功したときに処理を動かす |
onFailure |
失敗したときに処理を動かす |
pingBefore |
仕事の前に URL へ知らせる |
thenPing |
仕事の後に URL へ知らせる |
pingOnSuccess |
成功したときに URL へ知らせる |
pingOnFailure |
失敗したときに URL へ知らせる |
pingBeforeIf |
条件が true のとき、仕事の前に URL へ知らせる |
thenPingIf |
条件が true のとき、仕事の後に URL へ知らせる |
pingOnSuccessIf |
条件が true のとき、成功したら URL へ知らせる |
pingOnFailureIf |
条件が true のとき、失敗したら URL へ知らせる |
スケジュールのイベント#
スケジューラーは、予定を処理する途中で、いろいろなイベントを出します。次のイベントのどれにも、リスナーを作れます。
Illuminate\Console\Events\ScheduledTaskStarting
Illuminate\Console\Events\ScheduledTaskFinished
Illuminate\Console\Events\ScheduledBackgroundTaskFinished
Illuminate\Console\Events\ScheduledTaskSkipped
Illuminate\Console\Events\ScheduledTaskFailed
上から順に、仕事が始まるとき・終わったとき・裏で動かした仕事が終わったとき・飛ばされたとき・失敗したときに出ます。
関連するページ#
公式ドキュメント(英語)
2026年10月5日時点の内容をもとに、日本語でまとめています。