How to keep Laravel Horizon active with a cron job (New package)

Ralph J. Smit Laravel Software Engineer

Laravel Horizon is a great tool for managing and monitoring your Redis queues in Laravel. It allows you to spin up queues fast and make use of all the awesome Laravel Queue features.

However, there's one downside to Laravel Horizon. That downside is that Laravel Horizon does not restart the queues automatically if the process was terminated for some or the other reason.

Consider the scenario that you have a queue running, something unexpected happens and the queue shuts down. Once you discover this, it's usually too late and a lot of damage might have been done. (A relatively very small queue that handles 10 jobs a minute, would have 14,400 pending jobs if you discover this after one day!)

In order to solve this problem, the Laravel docs advise to use Supervisor to monitor the Horizon process. Sounds complex, but it boils down to the fact that Supervisor will keep an eye on the status of your Horizon process and automatically restarts it when the process is not running. Great, right?

The problem: installing Supervisor is not always possible/easy

However, as great as this might sound, installing Supervisor is not very straightforward if you have a relatively simple hosting plan, like shared hosting. Or perhaps there are restrictions to how you may use your VPS or other plan.

This means that when you don't have your own server, you're missing out on all the benefits of Laravel Horizon.

I also experienced this problem, so I went looking if there was an alternative/good replacement for Supervisor. It turned out that there really wasn't a good alternative. Then I found this Stack Overflow question from people having the same problem.

The solution proposed here is a simple PHP script that does the same job as Supervisor does. So I started thinking: could I put that in an easy reusable package?

I had never made a package before, so I went to explore how to do it. I learned a lot from the site laravelpackage.com, which is a truly great resource for learning package development. I also had a look at the source code of a few other packages I use in my daily workflow.

The solution: a Supervisor-alternative that uses cron-jobs

For the package, I decided to make use of Laravel's scheduler. If you're not familiar with the scheduler, it is in fact a nice PHP wrapper around cron jobs. You register only one cron job that triggers the scheduler every minute, and the scheduler will automatically check in PHP if it needs to run any code.

I also registered a new artisan command, php artisan supervisor:check. If you run this command (after installing the package of course), it will check whether Horizon is active. If it's not active, it will restart Laravel Horizon.

In the package I registered the code for the scheduler. This code runs the above artisan command every three minutes. This ensures that if Horizon stops working, it will be restarted in less than three minutes. Personally I think that three minutes is just right, as it doesn't put too much (mostly unnecessary) strain on the server, but it also ensures that Horizon is not inactive for too long.

You wouldn't want Horizon to be inactive for ten minutes on a server that runs 500 jobs a minute.

How to install the package

Prerequisites: configure the Laravel scheduler

In order to use this Laravel package, you need to have the Laravel Scheduler configured. This is very simple and it requires only a single cron job that runs every minute. Chances are you've already defined this. If not, make sure to add the following cron job to your server:

* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1

  • The part with * * * * * means that this cron job is run every minute.
  • The part cd /path-to-your-project && php artisan schedule:run is the actual action that is run with the cron job.
  • The part with /dev/null 2>&1 means that the cron job is running without any output. (Make sure to catch and handle exceptions in your application yourself.)

If you don't know where to add this cron job, please contact your hosting provider. A cron job is regarded as something very basic, so (in general) every hosting provider has it.

Replacing Supervisor

Now that we've had had the most complex part, we can go on to the fun part: replacing Supervisor.

Installing the package is very simple. Just run the following command to install the package:

composer require ralphjsmit/laravel-horizon-cron-supervisor

Everything will be configured automatically. From now on, your Laravel Horizon will be running almost always.

What about deployments?

Good question. When you deploy your app, you usually shut down your queues and your Laravel Horizon instance. If you don't, the queues will not get any code changes, until they are restarted. That's why you need to shut them down during deployment.

But here's a problem: what if the package restarts Horizon during your deployment process?

Luckily, the solution here is very easy. The Laravel Scheduler is disabled when you run php artisan down. So if you run php artisan down before shutting down your queues, and running php artisan up after restarting them, you're safe!

In the GitHub readme I added an example of a simplified typical deployment workflow:

# Prepare deployment
 
php artisan down
php artisan horizon:terminate
 
# Do deployment logic
# Horizon will not be restarted until you put the application out of maintenance mode
 
php artisan horizon
php artisan up
 
# Finish up deployment

Wrapping up

I hope this package will be useful for your use case in running Laravel Horizon without Supervisor! It certainly was for me. If it was useful and you've saved yourself a lot of hassle and perhaps a few bucks, I'd highly appreciate if if you could spare a minute and star the repo on GitHub✨.

As always, feel free to open an issue or leave a comment with your ideas and questions!

Published by Ralph J. Smit on in Laravel . Last updated on 11 March 2022 .