Automatizar tareas es una necesidad en cualquier aplicación web. Limpiar cachés, enviar recordatorios, generar reportes o sincronizar datos son operaciones que no queremos ejecutar manualmente. Laravel resuelve esto con su Task Scheduler, un sistema que permite definir todas las tareas programadas directamente en PHP, olvidándonos de gestionar múltiples entradas cron en el servidor.
Requisitos
Para seguir esta guía necesitas Laravel 11 o superior, ya que la configuración del scheduler se realiza en routes/console.php.
El concepto: un único cron para todo
El Task Scheduler funciona con una premisa simple: configuras una única entrada cron en tu servidor que ejecuta schedule:run cada minuto. Laravel se encarga del resto, evaluando qué tareas deben ejecutarse en ese momento.
* * * * * cd /ruta-a-tu-proyecto && php artisan schedule:run >> /dev/null 2>&1Esta línea es todo lo que necesitas en tu servidor. A partir de aquí, todo se gestiona desde tu código PHP.
Configuración básica
Las tareas programadas se definen en routes/console.php utilizando el facade Schedule:
<?php
use Illuminate\Support\Facades\Schedule;
Schedule::command('model:prune')->daily();Este código ejecutará el comando model:prune una vez al día. No necesitas tocar el servidor para añadir, modificar o eliminar tareas.
Formas de programar tareas
Laravel ofrece varios enfoques para definir qué se ejecuta.
Comandos Artisan
La forma más común. Puedes ejecutar cualquier comando Artisan, ya sea de Laravel o personalizado:
<?php
use Illuminate\Support\Facades\Schedule;
// Comandos nativos de Laravel
Schedule::command('cache:clear')->weekly();
Schedule::command('queue:prune-batches')->daily();
// Comando personalizado con argumentos
Schedule::command('emails:send --force')->dailyAt('08:00');Closures
Para tareas simples que no justifican crear un comando:
<?php
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schedule;
use App\Models\User;
Schedule::call(function () {
Log::info('Heartbeat: la aplicación sigue viva');
})->everyFiveMinutes();
Schedule::call(fn () => User::where('trial_ends_at', '<', now())->update(['status' => 'expired']))->daily();Jobs
Si ya tienes jobs definidos, puedes programarlos directamente:
<?php
use Illuminate\Support\Facades\Schedule;
use App\Jobs\ProcessDailyReports;
use App\Jobs\SyncInventory;
Schedule::job(new ProcessDailyReports)->dailyAt('06:00');
Schedule::job(new SyncInventory, 'sync-queue')->everyThirtyMinutes();El segundo parámetro permite especificar la cola donde se despachará el job.
Comandos del sistema
Para ejecutar comandos shell directamente:
<?php
use Illuminate\Support\Facades\Schedule;
Schedule::exec('node /home/forge/script.js')->daily();Frecuencias de ejecución
Laravel proporciona una API expresiva para definir cuándo se ejecutan las tareas.
Frecuencias básicas
<?php
use Illuminate\Support\Facades\Schedule;
Schedule::command('task:run')->everyMinute();
Schedule::command('task:run')->everyTwoMinutes();
Schedule::command('task:run')->everyFiveMinutes();
Schedule::command('task:run')->everyTenMinutes();
Schedule::command('task:run')->everyFifteenMinutes();
Schedule::command('task:run')->everyThirtyMinutes();
Schedule::command('task:run')->hourly();
Schedule::command('task:run')->hourlyAt(17); // En el minuto 17 de cada hora
Schedule::command('task:run')->everyTwoHours();
Schedule::command('task:run')->daily(); // A medianoche
Schedule::command('task:run')->dailyAt('13:00');
Schedule::command('task:run')->twiceDaily(1, 13); // A la 1:00 y 13:00
Schedule::command('task:run')->weekly();
Schedule::command('task:run')->weeklyOn(1, '8:00'); // Lunes a las 8:00
Schedule::command('task:run')->monthly();
Schedule::command('task:run')->monthlyOn(4, '15:00'); // Día 4 a las 15:00
Schedule::command('task:run')->quarterly();
Schedule::command('task:run')->yearly();Frecuencias personalizadas con cron
Cuando necesitas más control, puedes usar expresiones cron directamente:
<?php
use Illuminate\Support\Facades\Schedule;
// Cada día a las 3:30 AM
Schedule::command('backup:run')->cron('30 3 * * *');
// Cada 5 minutos durante horario laboral (9-18h)
Schedule::command('sync:check')->cron('*/5 9-18 * * *');Restricciones adicionales
Puedes encadenar métodos para restringir cuándo se ejecuta una tarea.
Por día de la semana
<?php
use Illuminate\Support\Facades\Schedule;
Schedule::command('reports:generate')
->daily()
->weekdays(); // Solo de lunes a viernes
Schedule::command('maintenance:run')
->daily()
->sundays(); // Solo domingos
// Métodos disponibles: mondays(), tuesdays(), wednesdays(),
// thursdays(), fridays(), saturdays(), sundays(), weekdays(), weekends()Por horario
<?php
use Illuminate\Support\Facades\Schedule;
// Solo entre las 8:00 y las 17:00
Schedule::command('notifications:send')
->everyFifteenMinutes()
->between('8:00', '17:00');
// Excepto entre las 2:00 y las 4:00 (ventana de mantenimiento)
Schedule::command('heavy:process')
->hourly()
->unlessBetween('2:00', '4:00');Por entorno
<?php
use Illuminate\Support\Facades\Schedule;
// Solo en producción
Schedule::command('analytics:process')->daily()->environments(['production']);
// Solo en staging y producción
Schedule::command('cache:warm')->hourly()->environments(['staging', 'production']);Condiciones dinámicas
<?php
use Illuminate\Support\Facades\Schedule;
use App\Models\Setting;
// Ejecutar solo si se cumple una condición
Schedule::command('emails:promotional')
->daily()
->when(fn () => Setting::get('promotional_emails_enabled'));
// Saltar si se cumple una condición
Schedule::command('sync:external')
->hourly()
->skip(fn () => app()->isDownForMaintenance());Control de ejecución
Estas opciones son críticas en entornos de producción.
Evitar solapamientos
Si una tarea tarda más de lo esperado, podrías terminar con múltiples instancias ejecutándose simultáneamente:
<?php
use Illuminate\Support\Facades\Schedule;
Schedule::command('reports:heavy')->hourly()->withoutOverlapping();Por defecto, el bloqueo expira a las 24 horas. Puedes personalizarlo:
<?php
use Illuminate\Support\Facades\Schedule;
// El bloqueo expira a los 30 minutos
Schedule::command('reports:heavy')->hourly()->withoutOverlapping(30);Ejecutar en un solo servidor
En entornos con múltiples servidores, evita que la misma tarea se ejecute en todos:
<?php
use Illuminate\Support\Facades\Schedule;
Schedule::command('payments:process')->daily()->onOneServer();Esto requiere que uses database, memcached, dynamodb o redis como driver de caché.
Ejecutar en segundo plano
Para tareas pesadas que no deben bloquear el scheduler:
<?php
use Illuminate\Support\Facades\Schedule;
Schedule::command('backup:database')->daily()->runInBackground();Modo mantenimiento
Por defecto, las tareas no se ejecutan cuando la aplicación está en modo mantenimiento. Para forzar la ejecución:
<?php
use Illuminate\Support\Facades\Schedule;
Schedule::command('health:check')->everyMinute()->evenInMaintenanceMode();Salida y monitorización
Guardar salida en archivos
<?php
use Illuminate\Support\Facades\Schedule;
// Sobrescribir el archivo
Schedule::command('sync:run')->daily()->sendOutputTo('/var/log/sync.log');
// Añadir al archivo
Schedule::command('sync:run')->daily()->appendOutputTo('/var/log/sync.log');Notificaciones por email
<?php
use Illuminate\Support\Facades\Schedule;
// Enviar siempre la salida
Schedule::command('reports:generate')
->daily()
->emailOutputTo('[email protected]');
// Enviar solo si falla
Schedule::command('reports:generate')
->daily()
->emailOutputOnFailure('[email protected]');Requiere configurar el sistema de correo de Laravel.
Hooks: antes y después
Los hooks permiten ejecutar código adicional alrededor de tus tareas.
<?php
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schedule;
Schedule::command('import:products')
->daily()
->before(function () {
Log::info('Iniciando importación de productos');
})
->after(function () {
Log::info('Importación de productos completada');
});Hooks según resultado
<?php
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schedule;
Schedule::command('critical:task')
->hourly()
->onSuccess(function () {
Http::post('https://monitoring.example.com/webhook', [
'status' => 'success',
'task' => 'critical:task',
]);
})
->onFailure(function () {
Log::critical('La tarea crítica ha fallado');
});Webhooks integrados
Laravel incluye métodos para notificar a URLs directamente:
<?php
use Illuminate\Support\Facades\Schedule;
Schedule::command('backup:run')
->daily()
->pingBefore('https://monitoring.example.com/start')
->thenPing('https://monitoring.example.com/end')
->pingOnSuccess('https://monitoring.example.com/success')
->pingOnFailure('https://monitoring.example.com/failure');Desarrollo local
Ejecutar el scheduler manualmente
Durante el desarrollo, puedes ejecutar el scheduler una vez:
php artisan schedule:runSimular la ejecución continua
Para simular el comportamiento del cron en local:
php artisan schedule:workEste comando ejecuta el scheduler cada minuto hasta que lo detengas con Ctrl+C.
Listar tareas programadas
Para ver todas las tareas configuradas:
php artisan schedule:listEsto muestra cada tarea con su próxima ejecución programada.
Probar una tarea específica
php artisan schedule:testEste comando interactivo te permite seleccionar y ejecutar una tarea específica.
Ejemplo práctico completo
Un archivo routes/console.php típico en producción podría verse así:
<?php
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schedule;
use App\Jobs\ProcessAnalytics;
// Mantenimiento básico
Schedule::command('model:prune')->daily()->at('01:00');
Schedule::command('queue:prune-batches')->daily()->at('01:30');
Schedule::command('cache:prune-stale-tags')->hourly();
// Backups - solo en producción, en un solo servidor
Schedule::command('backup:run')
->dailyAt('02:00')
->environments(['production'])
->onOneServer()
->withoutOverlapping()
->pingOnSuccess(config('services.monitoring.backup_url'));
// Sincronización externa - horario laboral
Schedule::command('sync:crm')
->everyFifteenMinutes()
->between('08:00', '20:00')
->weekdays()
->withoutOverlapping(10)
->onFailure(fn () => Log::error('Fallo en sincronización CRM'));
// Reportes diarios
Schedule::command('reports:daily')
->dailyAt('07:00')
->runInBackground()
->emailOutputOnFailure('[email protected]');
// Health check
Schedule::call(fn () => Http::timeout(5)->get(config('app.url') . '/health'))
->everyMinute()
->evenInMaintenanceMode()
->onFailure(fn () => Log::critical('Health check fallido'));
// Job programado
Schedule::job(new ProcessAnalytics)
->hourly()
->environments(['production']);Conclusión
El Task Scheduler de Laravel elimina la complejidad de gestionar crontabs en el servidor. Con una única entrada cron y código PHP expresivo, puedes controlar completamente cuándo y cómo se ejecutan tus tareas automatizadas. Las opciones de control como withoutOverlapping() y onOneServer() son especialmente valiosas en producción, donde la robustez es prioritaria.
Curso Laravel 12
Completo 2026
El único curso 100% actualizado que incluye Laravel 12, Livewire 3, Vue 3, React 19 e Inertia 2. Aprende con proyectos reales y las últimas funcionalidades.
star Incluido en cualquier suscripción