Laravel定时任务必须注册在app/Console/Kernel.php的schedule方法中,通过$schedule->command()或$schedule->call()声明;服务器需配置系统crontab每分钟执行php artisan schedule:run,并确保用户权限、路径和环境一致。

定时任务写在哪:必须放在 app/Console/Kernel.php 的 schedule 方法里
所有 Laravel 定时任务的注册入口只有这一个地方,不是在 routes/console.php,也不是在单独的命令类里。漏掉这步,php artisan schedule:run 就什么都不会执行。
常见错误是把任务逻辑直接写在命令类的 handle 里,却忘了在 Kernel.php 中调用 $schedule->command(...) 或 $schedule->call(...) 注册它。
-
$schedule->command('inspire')->hourly();—— 调用已注册的 Artisan 命令 -
$schedule->call(function () { \Log::info('ran'); })->daily();—— 直接执行闭包(适合简单逻辑) - 自定义命令必须先通过
php artisan make:command SendDailyReport创建,并在app/Console/Commands/下实现,再注册
怎么让服务器真正跑起来:Linux crontab 只需一行,但位置和用户很关键
Laravel 的调度器本身不是守护进程,它依赖系统级 crontab 每分钟拉一次 php artisan schedule:run。不配 crontab,本地 php artisan schedule:run 手动执行没用。
最容易踩的坑是用错了用户权限或路径。比如用 root 写了 crontab,但项目文件属主是 www-data,导致日志写入失败、队列无法推送。
- 推荐用项目所属用户编辑 crontab:
sudo -u www-data crontab -e - 写入标准行:
* * * * * cd /var/www/myapp && php artisan schedule:run >> /dev/null 2>&1 - 注意
cd切换到项目根目录,否则artisan可能找不到配置或加载不到服务提供者 - 别用
php /var/www/myapp/artisan绝对路径调用——如果 PHP CLI 版本和 Web 不一致,可能出环境差异问题
时间表达式别硬背 cron:Laravel 提供了语义化方法,但要注意底层仍受系统 cron 限制
->daily()、->hourly() 这些方法看着方便,但它们只是封装了 cron 表达式。真正生效的是 schedule:run 每分钟检查一次“该不该触发”,所以精度上限就是 1 分钟。
如果你写 ->at('13:00'),它不会在 13:00:00 精确执行,而是在 13:00:00–13:00:59 之间的某次 schedule:run 中触发——取决于 cron 实际运行时刻。
- 需要秒级控制?Laravel 原生不支持,得改用系统 cron 直接调用脚本,或引入第三方包如
spatie/laravel-pulse配合外部调度器 -
->twiceDaily(1, 13)等价于0 1,13 * * *,但实际执行时间仍由每分钟的schedule:run决定 - 开发环境想跳过等待?可用
php artisan schedule:work(Laravel 9+),它起一个常驻进程模拟调度,但仅限调试,不要上生产
日志和错误不显示?默认静默失败,必须手动加异常捕获和日志落盘
定时任务出错时,默认不会报错到终端,也不会发邮件,甚至不会写入 Laravel 日志——除非你显式处理。线上任务挂了几天没人发现,大概率是因为这个。
根本原因是 schedule:run 内部吞掉了大部分异常,只在 debug 模式下抛出。生产环境 APP_DEBUG=false,就彻底安静了。
- 在闭包或命令的
handle中加try/catch,并用\Log::error()记录堆栈 - 给命令加
--no-interaction --env=production参数确保行为一致,避免因环境变量缺失导致失败 - 检查
storage/logs/schedule-*.log(Laravel 8.77+ 自动启用),但前提是没关掉schedule:run的日志开关 - 别依赖
dd()或dump()—— 它们会中断输出,导致 cron 认为任务卡死
最麻烦的其实是环境差异:本地测试正常,上生产后因为时区、PHP 扩展缺失、队列驱动未切到 sync 或权限问题,任务直接静默跳过。每次上线前,记得用 php artisan tinker 手动跑一遍 resolve(\Illuminate\Console\Scheduling\Schedule::class)->dueEvents(app()) 看有没有待触发任务。


















