Laravelのスケジューラで定期タスクを自動化する:cron1本から安全運用まで
- 作成日 2026.02.12
- その他
LaravelのTask Schedulingは、複数のcronをサーバーに散らす代わりに「cronは1本だけ」「実行内容はLaravel側でコード管理」に寄せられる仕組み。定期メール、集計、ファイル生成、期限切れ処理、外部API同期などを、実行間隔・重複防止・失敗通知・ログ・ロックまで含めて運用設計できる。この記事は、導入の最短ルートから、実務で事故を減らす書き方・監視・キューとの分担までをまとめる。
- 1. 全体像:cronは1本、実行定義はapp側(コード管理)
- 2. 導入手順:まずは“毎分1本”のcronを入れる
- 3. 定義場所:スケジュールは“アプリの一箇所”に集約する
- 4. 基本例:コマンドを作って毎日実行する
- 5. スケジュール登録:毎日・毎時・毎分をコードで明示する
- 6. タイムゾーン:サーバー時刻とアプリ時刻のズレで事故が起きる
- 7. 重複実行対策:withoutOverlapping で二重起動を防ぐ
- 8. 多重サーバー対策:onOneServer で“1台だけ”に限定する
- 9. 失敗通知:onFailure / onSuccess で結果を外に出す
- 10. ログ運用:appendOutputTo で“後から追える”状態にする
- 11. 長時間処理:スケジュールは“起動”だけ、重い処理はキューへ逃がす
- 12. 環境ごとの制御:stagingだけ/productionだけを明示する
- 13. 実行確認:schedule:list と schedule:run で動作を潰す
- 14. よくあるトラブル:動かない/二重実行/時間ズレの典型原因
- 15. まとめ:Laravelスケジューラを安全に回すための最小セット
全体像:cronは1本、実行定義はapp側(コード管理)
基本は「毎分 artisan schedule:run を叩くcron」を1本置き、実際に動かすタスクはLaravelのKernel(Laravel 11以降は routes/console.php 等の構成に合わせて)で定義する。
これにより、タスク追加/修正はデプロイで反映でき、サーバーに手作業でcronを増やさずに済む。
導入手順:まずは“毎分1本”のcronを入れる
Linuxサーバーでの最小構成。実行ユーザー、PHPのパス、アプリのパスを間違えると動かないので、まずはここを固定する。
* * * * * cd /var/www/app && php artisan schedule:run >> /dev/null 2>&1
ポイント:
・cdでプロジェクト直下に移動
・php artisan を実行できるユーザー権限
・/dev/null へ捨てず、最初はログへ吐いて動作確認すると楽
定義場所:スケジュールは“アプリの一箇所”に集約する
スケジュールが複数箇所に散ると、運用で「どれがいつ動くのか」が追えなくなる。
・定義は一箇所にまとめる
・命名規則を付ける(後述の onSuccess/onFailure で識別に使える)
・環境差(stagingだけ/productionだけ)は明示する
基本例:コマンドを作って毎日実行する
定期タスクは “いきなりクロージャで書く” より “Artisanコマンド化” するとテスト・再実行・権限管理がやりやすい。
php artisan make:command CleanupOldFiles
// app/Console/Commands/CleanupOldFiles.php(例)
namespace App\Console\Commands;
use Illuminate\Console\Command;
class CleanupOldFiles extends Command
{
protected $signature = 'app:cleanup-old-files';
protected $description = 'Delete old temporary files';
public function handle(): int
{
// 実処理(例)
// Storage::disk('s3')->delete(...);
$this->info('done');
return self::SUCCESS;
}
}スケジュール登録:毎日・毎時・毎分をコードで明示する
運用で迷う原因は “何時に動くのかが曖昧” なこと。頻度は明示し、実行タイムゾーンも意識する。
Schedule::command('app:cleanup-old-files')
->dailyAt('03:10')
->withoutOverlapping(60) // 分:ロック有効期間
->name('cleanup-old-files');タイムゾーン:サーバー時刻とアプリ時刻のズレで事故が起きる
「毎日3時」のつもりが、サーバーがUTCで動いていてズレるのはよくある。
・appのtimezone(config/app.php)
・サーバーOSのtimezone
・DBのtimezone
この3つが混ざると、日次集計や締め処理が壊れやすい。スケジュールは “どのタイムゾーン基準か” を決めて統一する。
重複実行対策:withoutOverlapping で二重起動を防ぐ
タスクが想定より長引くと、次の実行タイミングで二重起動することがある。二重起動が許されない処理(請求、支払い、締め)には必須。
Schedule::command('app:cleanup-old-files')
->dailyAt('03:10')
->onOneServer()
->withoutOverlapping()
->name('cleanup-old-files');多重サーバー対策:onOneServer で“1台だけ”に限定する
Webサーバーが複数台あると、全台で schedule:run が走り、同じタスクが台数分動く。
onOneServer は分散ロック(キャッシュ/Redisなど)を使って “1台だけ” 実行させる。
Schedule::command('app:cleanup-old-files')
->dailyAt('03:10')
->onOneServer()
->withoutOverlapping()
->name('cleanup-old-files')
->onFailure(function () {
logger()->error('schedule failed: cleanup-old-files');
// Slack通知などをここで実施(実装は環境に合わせる)
})
->onSuccess(function () {
logger()->info('schedule success: cleanup-old-files');
});前提:キャッシュドライバが複数台で共有されていること(Redisなど)。ファイルキャッシュだと台ごとに分離されて成立しない。
失敗通知:onFailure / onSuccess で結果を外に出す
スケジューラは“静かに失敗”しやすい。最低限、失敗時通知を付けると復旧が早い。
Schedule::command('app:cleanup-old-files')
->dailyAt('03:10')
->appendOutputTo(storage_path('logs/schedule-cleanup.log'))
->name('cleanup-old-files');ログ運用:appendOutputTo で“後から追える”状態にする
/dev/null に捨てる運用は、障害時に何も残らない。
・最初はファイルへ吐く
・安定したらログ基盤へ集約(CloudWatch等)
・タスクごとにログを分けると追いやすい
Schedule::command('app:cleanup-old-files')
->dailyAt('03:10')
->appendOutputTo(storage_path('logs/schedule-cleanup.log'))
->name('cleanup-old-files');長時間処理:スケジュールは“起動”だけ、重い処理はキューへ逃がす
スケジュールが直接重い処理を抱えると、タイムアウト・重複・失敗リトライが難しくなる。
推奨:
・スケジュールはジョブ投入(dispatch)まで
・実処理はキューで分割
・再試行や冪等性はジョブ側で担保
これで「定期実行のトリガー」と「処理本体」が分離される。
// 例:定期的にジョブを投入する
use App\Jobs\RebuildDailyReport;
Schedule::job(new RebuildDailyReport())
->dailyAt('02:30')
->onOneServer()
->name('rebuild-daily-report');環境ごとの制御:stagingだけ/productionだけを明示する
検証環境で本番同等の処理を回すと、外部APIやメールが暴発することがある。環境で分ける。
if (app()->environment('production')) {
Schedule::command('app:cleanup-old-files')
->dailyAt('03:10')
->onOneServer()
->withoutOverlapping()
->name('cleanup-old-files');
}実行確認:schedule:list と schedule:run で動作を潰す
cronを待たずに、手元で “定義されているか/動くか” を確認してから本番に入れる。
php artisan schedule:list
php artisan schedule:runschedule:run は “実行対象があるタイミング” でないと何も起きないので、minutes単位のタスクを一時的に置くと確認しやすい。
よくあるトラブル:動かない/二重実行/時間ズレの典型原因
動かない:
・cronのユーザーが違う、パスが違う、cdしていない
・php のパスが違う(複数バージョン)
・.env が読めていない(権限/パス)
二重実行:
・複数台で回して onOneServer が無い
・タスクが長引き withoutOverlapping が無い
時間ズレ:
・サーバーUTC、アプリJST、DB別タイムゾーンなどが混在
“動かない”はログの出し方を整えると即解決に寄る。
まとめ:Laravelスケジューラを安全に回すための最小セット
・cronは毎分1本、実行定義はアプリでコード管理
・コマンド化して再実行/テスト/監視をしやすくする
・withoutOverlapping と onOneServer で重複実行を潰す
・失敗通知とログを必ず付ける(黙って落ちるのを防ぐ)
・重い処理はキューへ逃がし、スケジューラは“起動役”にする
・タイムゾーンと環境差を明示して事故を減らす
-
前の記事
Laravelのイベントとリスナーでアプリを拡張する:疎結合にして変更に強い実装へ 2026.02.10
-
次の記事
記事がありません
コメントを書く