Laravel调度器本身不驻留运行,必须依赖系统Cron每分钟执行php artisan schedule:run才能触发任务;本地手动执行仅瞬时判断并退出,需配置正确路径、用户、PHP路径、时区及日志重定向。

直接跑 php artisan schedule:run 没反应,不是你写错了,是它本来就不该“一直运行”——Laravel 调度器只是个检查器,必须靠系统 Cron 每分钟推一把才生效。
为什么 schedule:run 本地执行没效果
这个命令只做一次判断:当前有没有任务到期?有就执行,完事退出。它不监听、不驻留、不轮询。你在本地敲一遍,它跑完就停,不会等下一分钟。
- 常见错误现象:改完
Kernel.php就直接php artisan schedule:run,发现任务没跑、日志空、时间到了也没动静 - 根本原因:调度逻辑在 PHP 进程里是瞬时的,没有后台守护机制
- 正确做法:把它交给 Linux 的
cron,每分钟自动触发一次 - 验证方式:手动执行后立刻查
storage/logs/laravel.log或你指定的输出文件,看是否有 “Running scheduled command” 日志
怎么配系统级 Cron 才不踩坑
关键不在“加一行”,而在路径、用户、权限、重定向这四点全对上。
-
crontab -e必须用部署应用的用户(比如www-data或deploy),不能用sudo crontab -e—— 否则读不到.env、连不上 Redis、写不了storage/ -
cd路径必须是绝对路径,且要能访问到artisan和bootstrap/app.php;推荐写成cd /var/www/myapp && /usr/bin/php artisan schedule:run,显式指定 PHP 路径更稳 -
>> /dev/null 2>&1是默认写法,但如果要排查问题,临时改成> /var/log/laravel-schedule.log 2>&1,方便抓输出 - 确认 cron 服务活着:
sudo systemctl status cron(Debian/Ubuntu)或sudo systemctl status crond(CentOS/RHEL)
withoutOverlapping() 在多实例下为啥还重复执行
它默认用的是 file 或 array 缓存驱动,锁只在单机内存里生效。三台服务器同时跑,每台都觉得自己是唯一,结果任务跑三遍。
- 解决路径只有两个:换共享缓存,或显式指定锁 store
- 推荐配 Redis:确保
config/cache.php中'default' => 'redis',再写$schedule->command('backup:db')->hourly()->withoutOverlapping(3600, 'redis') - 注意:如果 Redis 不可用,
withoutOverlapping()会静默跳过锁逻辑,任务照常执行 —— 这点容易被忽略,得配合 Redis 健康检查 - 闭包任务(
$schedule->call(...))无法进队列,也不能用onQueue(),必须拆成自定义命令类才能走队列
开发时怎么快速验证调度逻辑是否写对
别等 cron 一分钟一跑,用 schedule:list 和 schedule:work 组合验证最准。
-
php artisan schedule:list:列出所有注册的任务、频率、下次运行时间,一眼看出dailyAt('03:00')是否被解析成预期时间(注意时区!) -
php artisan schedule:work:启动一个长进程,每秒检查一次,适合本地调试;但它依赖 Laravel 的事件循环,生产环境别用 - 时区陷阱:Laravel 默认用服务器时区,但
dailyAt()解析时间时按config/app.php的'timezone'来,两者不一致会导致“明明写了 09:00 却在 01:00 执行” - 临时测试某任务:加
->when(fn() => true)或直接注释掉其他任务,避免干扰
真正难的不是写那行 * * * * * cd ...,而是让 cron 用户、PHP 解释器路径、缓存驱动、时区、日志落盘位置这五样东西在所有环境里咬合严丝合缝。少一个,调度就可能静默失效。


















