Laravel计划任务必须同时满足两个条件:一是在app/Console/Kernel.php的schedule()方法中注册任务,二是在服务器crontab中每分钟执行php artisan schedule:run;缺一则任务静默失效。

Laravel计划任务不会自动运行,必须同时满足两个硬性条件:任务注册在app/Console/Kernel.php的schedule()方法里,且服务器系统级crontab每分钟调用一次php artisan schedule:run。漏掉任一环节,任务就静默失效。
任务写在哪?只认app/Console/Kernel.php里的schedule()
所有调度逻辑必须集中写在这个方法里,不是routes/console.php,也不是命令类的handle()里直接放逻辑。常见错误是写了自定义命令SendDailyReport,却忘了在schedule()中调用$schedule->command('send:daily-report')注册它。
注册方式分两类:
-
$schedule->command('inspire'):调用已注册的 Artisan 命令,走完整命令生命周期(中间件、异常捕获、日志通道、DB 事务等) -
$schedule->call(function () { ... }):直接执行闭包,不经过命令容器,轻量但无上下文保障
别在call()里用$this->info()或依赖注入服务——它没有$this指向命令实例。
服务器crontab怎么配?用户、路径、环境一个都不能错
只加这一行,但细节决定成败:
* * * * * cd /var/www/myapp && php artisan schedule:run >> /dev/null 2>&1
关键点:
- 必须用项目实际所属用户编辑:
sudo -u www-data crontab -e,不能用root写却让www-data跑文件 -
cd /var/www/myapp不可省略——否则artisan加载不到.env和自动加载规则,APP_ENV=production不生效会导致schedule:run静默退出 - 别写成
php /var/www/myapp/artisan schedule:run——CLI PHP 版本可能和 Web 不一致,引发环境差异
验证是否通:手动运行php artisan schedule:run,看终端输出是否有Running scheduled command或明确报错。
时间不准?时区、精度、at()陷阱全在这儿
Laravel 调度器解析时间只认config/app.php里的'timezone'配置项,且必须是 IANA 格式(如'Asia/Shanghai'),不是date_default_timezone_set()或服务器tz设置。
精度上限就是 1 分钟:即使你写->dailyAt('02:00'),它也不会在 02:00:00 精确触发,而是在 02:00:00–02:00:59 之间某次schedule:run检查时执行——取决于系统cron实际拉起时间。
常见陷阱:
- 服务器时区是 UTC,
config/app.php没设timezone→->dailyAt('02:00')真正在北京时间 10:00 执行 - 改了
config/app.php但没清缓存:php artisan config:clear,新时区不生效 - 在
call()闭包里用now(),返回的是服务器本地时间,和调度器判断逻辑用的时区可能不一致 → 统一用Carbon::now(config('app.timezone'))
为什么withoutOverlapping()有时没用?多服务器场景要小心
withoutOverlapping()靠写文件锁(storage/framework/cache/schedule-*.lock)实现,只在单机有效。如果部署在多台服务器上,每台都跑自己的crontab,这个锁互不可见,任务照样重叠。
此时必须用onOneServer(),它依赖 Laravel 缓存驱动(Redis 推荐)做分布式锁。但前提是:
- 缓存驱动必须支持原子操作(Redis ✅,File ❌,Database ⚠️ 可能慢或失败)
- 所有服务器共享同一套缓存后端
- 命令本身不能有副作用(比如
onOneServer()失败时默认静默跳过,不会报错)
真正难调试的点往往不在语法,而在多服务器、跨时区、权限隔离这些隐性耦合上——它们不出错则已,一出就是凌晨三点的线上事故。


















