Qué es la programación de tareas
Tradicionalmente, cada tarea periódica requería añadir manualmente una entrada al cron del servidor.
El problema es que la definición vivía fuera del código fuente, sin control de versiones y con la necesidad de acceder por SSH para revisarla o modificarla.
Con el scheduler de Laravel defines la programación de tareas dentro de la propia aplicación con una API fluida.
Solo necesitas una entrada de cron en el servidor, y las tareas quedan versionadas junto al código.
Lo habitual es definir la programación en routes/console.php.
<?php
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schedule;
Schedule::call(function () {
DB::table('recent_users')->delete();
})->daily();
Con el comando Artisan schedule:list puedes ver todas las tareas definidas y su próxima ejecución.php artisan schedule:list
Flujo de ejecución del scheduler
Cómo definir la programación
La programación se define en routes/console.php.
También puedes definirla en el método withSchedule de bootstrap/app.php.
// bootstrap/app.php
use Illuminate\Console\Scheduling\Schedule;
->withSchedule(function (Schedule $schedule) {
$schedule->call(new DeleteRecentUsers)->daily();
})
Tipos de tareas programables
Comandos Artisan
Con command programas un comando Artisan.
Puedes pasar el nombre o la clase del comando.
use App\Console\Commands\SendEmailsCommand;
use Illuminate\Support\Facades\Schedule;
// Por nombre
Schedule::command('emails:send Taylor --force')->daily();
// Por clase (con argumentos en un array)
Schedule::command(SendEmailsCommand::class, ['Taylor', '--force'])->daily();
Los comandos Artisan definidos con closure también admiten el encadenamiento.
Artisan::command('delete:recent-users', function () {
DB::table('recent_users')->delete();
})->purpose('Delete recent users')->daily();
Jobs de cola
Con job programas un job de cola. Es una forma cómoda de encolarlo sin closures.
use App\Jobs\Heartbeat;
use Illuminate\Support\Facades\Schedule;
Schedule::job(new Heartbeat)->everyFiveMinutes();
También puedes indicar la cola y la conexión.
// Cola "heartbeats" y conexión "sqs"
Schedule::job(new Heartbeat, 'heartbeats', 'sqs')->everyFiveMinutes();
Comandos de shell
Con exec ejecutas un comando del sistema operativo.
use Illuminate\Support\Facades\Schedule;
Schedule::exec('node /home/forge/script.js')->daily();
Closures
Con call programas cualquier closure PHP.
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schedule;
Schedule::call(function () {
DB::table('recent_users')->delete();
})->daily();
También aceptas objetos invocables (con __invoke).
Schedule::call(new DeleteRecentUsers)->daily();
Configurar la frecuencia
Métodos más habituales
| Método | Descripción |
|---|
->everySecond() | Cada segundo |
->everyMinute() | Cada minuto |
->everyFiveMinutes() | Cada 5 minutos |
->everyFifteenMinutes() | Cada 15 minutos |
->everyThirtyMinutes() | Cada 30 minutos |
->hourly() | Cada hora |
->hourlyAt(17) | Cada hora al minuto 17 |
->daily() | Cada día a las 00:00 |
->dailyAt('13:00') | Cada día a las 13:00 |
->twiceDaily(1, 13) | Cada día a la 1 y a las 13 |
->weekly() | Cada domingo a las 00:00 |
->weeklyOn(1, '8:00') | Cada lunes a las 8:00 |
->monthly() | El día 1 de cada mes a las 00:00 |
->monthlyOn(4, '15:00') | El día 4 a las 15:00 |
->quarterly() | El primer día de cada trimestre a las 00:00 |
->yearly() | Cada 1 de enero a las 00:00 |
->timezone('Asia/Tokyo') | Establece la zona horaria |
Expresiones cron directamente
Con cron puedes pasar la expresión directamente.
use Illuminate\Support\Facades\Schedule;
Schedule::command('emails:send')->cron('0 9 * * *'); // Cada día a las 9:00
Combinar frecuencia y días
Combinando frecuencia y restricciones por día obtienes programaciones más finas.
use Illuminate\Support\Facades\Schedule;
// Cada lunes a las 13:00
Schedule::call(function () {
// ...
})->weekly()->mondays()->at('13:00');
// De lunes a viernes, cada hora entre las 8 y las 17
Schedule::command('foo')
->weekdays()
->hourly()
->timezone('Asia/Tokyo')
->between('8:00', '17:00');
Configurar la zona horaria
Con timezone indicas la zona horaria de una tarea concreta.
use Illuminate\Support\Facades\Schedule;
Schedule::command('report:generate')
->timezone('Asia/Tokyo')
->at('9:00');
Para una zona horaria común a todas las tareas, define schedule_timezone en config/app.php.
// config/app.php
'schedule_timezone' => 'Europe/Madrid',
Al usar zonas horarias con horario de verano, en el cambio de hora las tareas pueden ejecutarse dos veces o no ejecutarse.
Se recomienda usar UTC siempre que sea posible.
Restricciones condicionales
when / skip
when ejecuta la tarea solo si la closure devuelve true.
skip hace lo contrario: la salta si devuelve true.
use Illuminate\Support\Facades\Schedule;
// Ejecuta solo si es true
Schedule::command('emails:send')->daily()->when(function () {
return true;
});
// Salta si es true
Schedule::command('emails:send')->daily()->skip(function () {
return true;
});
environments
Con environments restringes a determinados entornos.
Schedule::command('emails:send')
->daily()
->environments(['staging', 'production']);
Restricciones horarias
Con between / unlessBetween limitas el rango horario.
// Solo entre las 7 y las 22
Schedule::command('emails:send')
->hourly()
->between('7:00', '22:00');
// No ejecutar entre las 23 y las 4
Schedule::command('emails:send')
->hourly()
->unlessBetween('23:00', '4:00');
Restricciones por día
| Método | Descripción |
|---|
->weekdays() | Solo días laborables |
->weekends() | Solo fines de semana |
->mondays() … ->sundays() | Un día concreto |
->days([0, 3]) | Varios días (0 = domingo, 3 = miércoles…) |
use Illuminate\Support\Facades;
use Illuminate\Console\Scheduling\Schedule;
Facades\Schedule::command('emails:send')
->hourly()
->days([Schedule::SUNDAY, Schedule::WEDNESDAY]);
Evitar solapamientos
Por defecto, aunque la ejecución anterior no haya terminado, se inicia la siguiente.
Con withoutOverlapping la nueva ejecución espera a que termine la anterior.
use Illuminate\Support\Facades\Schedule;
Schedule::command('emails:send')->withoutOverlapping();
Puedes indicar la caducidad del lock en minutos (por defecto 24 horas).
// El lock expira a los 10 minutos
Schedule::command('emails:send')->withoutOverlapping(10);
withoutOverlapping gestiona el lock en la caché de la aplicación.
Si una tarea queda atascada, puedes liberarlo con schedule:clear-cache.php artisan schedule:clear-cache
Ejecución en varios servidores
Cuando el scheduler se ejecuta en varios servidores, con onOneServer te aseguras de que solo uno ejecute la tarea.
use Illuminate\Support\Facades\Schedule;
Schedule::command('report:generate')
->fridays()
->at('17:00')
->onOneServer();
Para usar esta funcionalidad, la caché por defecto debe ser database, memcached, dynamodb o redis, y todos los servidores deben apuntar al mismo backend.
Flujo de control distribuido con onOneServer()
Agrupar tareas
Con group aplicas la misma configuración a varias tareas.
use Illuminate\Support\Facades\Schedule;
Schedule::daily()
->onOneServer()
->timezone('Europe/Madrid')
->group(function () {
Schedule::command('emails:send --force');
Schedule::command('emails:prune');
});
Ejecución en segundo plano
Por defecto, las tareas programadas al mismo tiempo se ejecutan en el orden en el que se definen.
Si una es lenta, retrasa a las siguientes. Con runInBackground se ejecutan en paralelo.
use Illuminate\Support\Facades\Schedule;
Schedule::command('analytics:report')
->daily()
->runInBackground();
runInBackground solo se puede usar con command y exec.
Modo mantenimiento
En modo mantenimiento no se ejecutan las tareas programadas.
Para forzar la ejecución incluso en mantenimiento, usa evenInMaintenanceMode.
Schedule::command('emails:send')->evenInMaintenanceMode();
Flujo de decisión en modo mantenimiento
Pausar el scheduler
Puedes pausarlo sin modificar código.
# Pausa
php artisan schedule:pause
# Reanudar
php artisan schedule:continue
Si quieres que ciertas tareas se sigan ejecutando durante la pausa, márcalas con evenWhenPaused. Útil para health checks o monitorización.
Schedule::command('emails:send')->evenWhenPaused();
Manejo de la salida
Enviar la salida a un archivo
Con sendOutputTo guardas la salida.
use Illuminate\Support\Facades\Schedule;
Schedule::command('emails:send')
->daily()
->sendOutputTo(storage_path('logs/emails-send.log'));
Con appendOutputTo la añades al final del archivo existente.
Schedule::command('emails:send')
->daily()
->appendOutputTo(storage_path('logs/emails-send.log'));
Enviar la salida por correo
Con emailOutputTo la salida se envía por correo (necesitas la configuración de correo de Laravel).
Schedule::command('report:generate')
->daily()
->sendOutputTo($filePath)
->emailOutputTo('[email protected]');
Para enviar solo cuando falla, usa emailOutputOnFailure.
Schedule::command('report:generate')
->daily()
->emailOutputOnFailure('[email protected]');
Hooks de tarea
Con before / after ejecutas lógica antes y después de la tarea.
use Illuminate\Support\Facades\Schedule;
Schedule::command('emails:send')
->daily()
->before(function () {
// Antes de ejecutar
})
->after(function () {
// Después de ejecutar
});
Con onSuccess / onFailure reaccionas al resultado.
use Illuminate\Support\Stringable;
Schedule::command('emails:send')
->daily()
->onSuccess(function (Stringable $output) {
// En caso de éxito
})
->onFailure(function (Stringable $output) {
// En caso de fallo
});
Despliegue en servidor
Añadir la entrada de cron
Con esta línea en el crontab del servidor, el scheduler se ejecutará cada minuto.* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
Ábrelo con crontab -e. Comprobar el scheduler
Muestra las tareas definidas y su próxima ejecución.php artisan schedule:list
Con Laravel Cloud puedes gestionar tareas programadas sin configurar cron.
Ejecución en desarrollo local
En local, en vez de usar cron, puedes mantener el scheduler activo con schedule:work.
php artisan schedule:work
Se ejecuta en primer plano e invoca al scheduler cada minuto. Se detiene con Ctrl+C.
Tareas sub-minuto (con periodicidad inferior a 1 minuto)
Cron usa un minuto como unidad mínima; en Laravel puedes programar tareas por segundos.
use Illuminate\Support\Facades\Schedule;
Schedule::call(function () {
DB::table('recent_users')->delete();
})->everySecond();
Si hay tareas sub-minuto, schedule:run sigue corriendo durante el minuto en curso para procesarlas todas.
En tareas sub-minuto conviene delegar el trabajo a jobs de cola o comandos en segundo plano.
Si la tarea es lenta, retrasa a las siguientes tareas sub-minuto.
Para interrumpir un schedule:run en curso durante un despliegue, añade esto al script:
php artisan schedule:interrupt
Comandos habituales
# Listado de tareas
php artisan schedule:list
# Ejecución manual (lo llama cron)
php artisan schedule:run
# Modo desarrollo (siempre activo)
php artisan schedule:work
# Pausar
php artisan schedule:pause
# Reanudar
php artisan schedule:continue
# Limpiar locks de solapamiento
php artisan schedule:clear-cache
# Interrumpir tareas sub-minuto (al desplegar)
php artisan schedule:interrupt