Laravel scheduler by supervisord running multiple time

Stefan Bogdanescu

Founder & Senior Architect · 2026-06-29

Laravel Company
# Mastering Laravel Scheduler with Supervisord: Why You're Seeing Multiple Executions As a senior developer, I frequently encounter situations where process management tools like Supervisord interact with application scheduling frameworks like Laravel’s, leading to unexpected behavior. The issue you are describing—where the Laravel scheduler runs multiple times instead of just once per cycle—is a common point of confusion. It usually stems from a misunderstanding of how the Artisan command interacts with the underlying system scheduler (like Cron) and how process supervisors manage execution loops. This post will dive deep into why this happens, analyze your provided configuration, and provide a robust solution to ensure your Laravel tasks execute precisely as intended. ## Understanding the Laravel Scheduling Mechanism The core of the problem lies in understanding what `php artisan schedule:run` actually does. This command is designed to check the database for scheduled jobs and execute any that are due *at that moment*. It is not a continuous loop; it's an execution point triggered by an external mechanism, typically cron. When you use Supervisord to manage this process, if the underlying system (like Cron) is triggering the command more frequently than expected, or if there's a slight delay in process reporting, it can create the illusion of repeated execution. In a well-architected Laravel application, scheduling should be managed by a reliable external scheduler, and Supervisord should focus on maintaining the health of that single execution unit, not managing the schedule itself. ## Analyzing Your Configuration Let's examine the configuration snippets you provided to pinpoint the potential friction points: ### Supervisord Configuration Review ```ini [program:laravel-scheduler] command=php /var/www/project-api/artisan schedule:run --verbose --no-interaction autorestart=true numprocs=1 user=nginx ... ``` This configuration looks fundamentally correct for running a single command continuously. The use of `numprocs=1` is good practice, ensuring only one instance of the scheduler runs at a time. However, if this process is being restarted frequently by an external mechanism unrelated to the schedule itself (perhaps a system watchdog or a poorly configured cron job), it will naturally restart and execute `schedule:run` repeatedly. ### Laravel ConsoleKernel Review ```php protected function schedule(Schedule $schedule) { $schedule->command('telescope:prune --hours=24')->daily(); $schedule->command('horizon:snapshot')->everyFiveMinutes(); // ... other jobs } ``` The definitions here are correct for defining *what* should run. The schedule object merely sets the rules; it doesn't execute them itself. The execution is deferred until the scheduler command (`schedule:run`) is invoked. ## The Solution: Separating Scheduling from Process Management To achieve the desired "one time per cycle" behavior, we need to decouple the scheduling trigger from the process supervisor. The most robust pattern for Laravel scheduling involves using the operating system's native cron mechanism as the sole trigger. ### Recommended Best Practice: Rely on Cron Instead of relying solely on Supervisord to keep the `schedule:run` command alive in an endless loop, let the Linux cron service handle the execution every minute. This is more reliable and less resource-intensive than a persistent background process managed by Supervisord for this specific task. **Step 1: Ensure Cron Triggers the Schedule Run** Configure your crontab to run the scheduler command every minute: ```bash # Run the Laravel scheduler command every minute * * * * * /usr/bin/php /var/www/project-api/artisan schedule:run >> /var/www/project-api/storage/logs/schedule.log 2>&1 ``` **Step 2: Adjust Supervisord's Role (If Necessary)** If you still wish to use Supervisord for monitoring the overall health of your application services, keep it running, but ensure it is *not* responsible for triggering the schedule itself. If the `schedule:run` command fails or exits unexpectedly, Supervisord will correctly detect this and restart it (due to `autorestart=true`), which is good error handling. ### Why This Works Better By using Cron as the trigger, you achieve true single execution per cycle because the system scheduler forces the execution exactly once per minute, regardless of how the Laravel application itself manages its internal scheduling queue. This separation ensures that: 1. **Reliability:** The schedule runs according to a known, robust system standard. 2. **Efficiency:** You avoid unnecessary process restarts caused by confusing feedback loops. This approach aligns perfectly with architectural principles—keeping application logic focused on business rules (like your Eloquent scheduling) and delegating timing/system tasks to the appropriate operating system tools. For more complex background task management, exploring services like those offered by [Laravel Company](https://laravelcompany.com) can provide insights into building scalable systems where timing is handled externally. ## Conclusion The issue of repeated scheduler execution is rarely a flaw in the Laravel code itself, but rather an interaction error between the application's internal logic and the external process management layer (Supervisord). By shifting the responsibility for triggering the schedule from the application-managed service to the reliable system-level Cron utility, you eliminate the ambiguity. This results in a cleaner, more predictable, and highly maintainable setup.