81

I recently setup a Laravel Queue system. The basics are a cronjob calls a command which adds jobs to a queue and calls a second command which sends an email.

The system works when I ssh into my server and run php artisan queue:listen, but if I close my terminal the listener shuts down and the jobs stack up and sit in queue until I ssh back in and run listen again.

What is the best way to keep my queue system running in the background without needing to keep my connection open via ssh?

I tried running php artisan queue:work --daemon, and it completed the jobs in the queue, but when I closed my terminal it closed the connection and the background process.

zeros-and-ones
  • 3,407
  • 3
  • 30
  • 50

17 Answers17

118

Running

nohup php artisan queue:work --daemon &

Will prevent the command exiting when you log out.

The trailing ampersand (&) causes process start in the background, so you can continue to use the shell and do not have to wait until the script is finished.

See nohup

nohup - run a command immune to hangups, with output to a non-tty

This will output information to a file entitled nohup.out in the directory where you run the command. If you have no interest in the output you can redirect stdout and stderr to /dev/null, or similarly you could output it into your normal laravel log. For example

nohup php artisan queue:work --daemon > /dev/null 2>&1 &

nohup php artisan queue:work --daemon > app/storage/logs/laravel.log &

But you should also use something like Supervisord to ensure that the service remains running and is restarted after crashes/failures.

Ben Swinburne
  • 22,620
  • 8
  • 55
  • 94
  • Ahah awesome! I think this is going to be the accepted answer! I'll be able to test soon. Thank you. – zeros-and-ones Feb 21 '15 at 19:32
  • AWesome..this made my day – sumit Mar 10 '17 at 00:00
  • 7
    First I needed: http://stackoverflow.com/a/29292637/470749 Then `nohup php artisan queue:work --daemon > storage/logs/laravel.log &` worked for me. Note: if you want to kill the nohup daemon, you need to first discover its PID by running something like `ps -ef |grep artisan`. Then you can run `kill [pid]` http://stackoverflow.com/q/17385794/470749 – Ryan Apr 17 '17 at 19:26
  • 1
    This is a poor solution because once you issue a queue:reset the worker dies and you don't have a restart mechanism, just use supervisord, spawn 2 workers and you will be good as gold when you have high volumes. As a side note, you will need to respawn new workers each time you make a code change. – z900collector Jun 28 '18 at 04:51
  • 2
    @z900collector The answer explicitly says you should use something like Supervisord and always has done. – Ben Swinburne Jun 28 '18 at 14:14
  • @BenSwinburne, have you heard of Linux screen? What about fg and bg? – zeros-and-ones Jul 24 '20 at 07:48
  • You just saved me 2 days. Thanks! – Erich García Mar 09 '21 at 06:21
35

You should use linux supervisor

Installation is simple and on Ubuntu I can install it with following command:

apt-get install supervisor

Supervisor configuration files are located in /etc/supervisor/conf.d directory.

[program:email-queue]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/laravel-example/artisan queue:work redis --queue=emailqueue --sleep=3 --tries=3
autostart=true
autorestart=true
user=forge
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/laravel-example//storage/logs/supervisord.log

For each process you should create a new process configuration file. With this configuration, listener will retry each job 3 times. Also Supervisor will restart listener if it fails or if system restarts.

Manish Nakar
  • 2,739
  • 1
  • 16
  • 13
  • 2
    more instruction is here, https://www.digitalocean.com/community/tutorials/how-to-install-and-manage-supervisor-on-ubuntu-and-debian-vps – namal Sep 23 '17 at 10:58
  • Hello, can you help with my problem? I'm using supervisord and the job was executed, but cant create a file https://stackoverflow.com/questions/47715537/laravel-supervisord-cannot-create-file – Irfandi D. Vendy Dec 08 '17 at 17:51
  • 1
    Worth noting this answer is also the Laravel documented way of doing things: https://laravel.com/docs/5.6/queues#supervisor-configuration – Martin Joiner Apr 04 '18 at 18:52
17

The command

nohup php artisan queue:work --daemon &

was correct, it would allow the process to continue after closing the SSH connection; however, this is only a short term fix. Once your server is rebooted or any issue causes the process to stop you will need to go back and run the command again. When that occurs, you never know. It could happen on a Friday night, so it is better to implement a long term solution.

I ended up switching over to Supervisord, this can be installed on Ubuntu as easy as

sudo apt-get install supervisor 

For AWS-AMI or RedHat users you can follow the set of instructions I outlined in this question:

Setting up Supervisord on a AWS AMI Linux Server

Community
  • 1
  • 1
zeros-and-ones
  • 3,407
  • 3
  • 30
  • 50
  • Hello, can you help with my problem? I'm using supervisord and the job was executed, but cant create a file https://stackoverflow.com/questions/47715537/laravel-supervisord-cannot-create-file – Irfandi D. Vendy Dec 08 '17 at 13:52
  • 1
    So why did you not accept the answer that suggested supervisor? – dewwwald Jan 08 '18 at 21:39
  • 2
    That answer only applies to Ubuntu users, my answer links to a separate question with how to set it up on RedHat based distros. Also, I answered Dev 15 2016, that other answer which is specific to only Ubuntu users came in June 2017. – zeros-and-ones Jan 08 '18 at 22:05
  • Not to be pedantic, but @deewwald was probably referring to [the answer here you said you would likely accept](https://stackoverflow.com/a/28625847/6089612) - AFAICT it was the first (meaningful) answer posted, it suggested Supervisor, and it is not specific to any OS. – Don't Panic Feb 04 '20 at 12:08
  • Ahh I agree that answer was meaningful, and answered the question, however it really is a bad solution. In order to have a queue process reliably some sort or process monitor should be integrated. The laravel community seems to lean towards Supervisor, however I have seen Monit used with success as well. – zeros-and-ones Feb 04 '20 at 21:56
  • Sticking with my pedantry, *it really is a bad solution* - AFAICT it is 100% correct. It is also practically identical to your own answer, which you accepted. It answers the original question, and goes on to suggest the better approach (Supervisor) - exactly as yours does. The biggest difference is the other question was posted 2 months earlier. As both you yourself and dewwwald said, it seems like it should be the accepted answer. Anyway, sorry for quibbling, they're both correct, and they helped me yesterday. – Don't Panic Feb 05 '20 at 11:25
  • I selected it as the correct answer are you happy now? – zeros-and-ones Feb 05 '20 at 19:52
13

From https://gist.github.com/ivanvermeyen/b72061c5d70c61e86875

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;

class EnsureQueueListenerIsRunning extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'queue:checkup';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Ensure that the queue listener is running.';

    /**
     * Create a new command instance.
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return void
     */
    public function handle()
    {
        if ( ! $this->isQueueListenerRunning()) {
            $this->comment('Queue listener is being started.');
            $pid = $this->startQueueListener();
            $this->saveQueueListenerPID($pid);
        }

        $this->comment('Queue listener is running.');
    }

    /**
     * Check if the queue listener is running.
     *
     * @return bool
     */
    private function isQueueListenerRunning()
    {
        if ( ! $pid = $this->getLastQueueListenerPID()) {
            return false;
        }

        $process = exec("ps -p $pid -opid=,cmd=");
        //$processIsQueueListener = str_contains($process, 'queue:listen'); // 5.1
        $processIsQueueListener = ! empty($process); // 5.6 - see comments

        return $processIsQueueListener;
    }

    /**
     * Get any existing queue listener PID.
     *
     * @return bool|string
     */
    private function getLastQueueListenerPID()
    {
        if ( ! file_exists(__DIR__ . '/queue.pid')) {
            return false;
        }

        return file_get_contents(__DIR__ . '/queue.pid');
    }

    /**
     * Save the queue listener PID to a file.
     *
     * @param $pid
     *
     * @return void
     */
    private function saveQueueListenerPID($pid)
    {
        file_put_contents(__DIR__ . '/queue.pid', $pid);
    }

    /**
     * Start the queue listener.
     *
     * @return int
     */
    private function startQueueListener()
    {
        //$command = 'php-cli ' . base_path() . '/artisan queue:listen --timeout=60 --sleep=5 --tries=3 > /dev/null & echo $!'; // 5.1
        $command = 'php-cli ' . base_path() . '/artisan queue:work --timeout=60 --sleep=5 --tries=3 > /dev/null & echo $!'; // 5.6 - see comments
        $pid = exec($command);

        return $pid;
    }
}
Harry Bosh
  • 2,630
  • 1
  • 27
  • 29
  • Thanks for sharing, that is a unique way to ensure a queue is running! Nice because requires installing no new dependencies, just need a handle on CRONTAB. Any permissions settings gotchas? – zeros-and-ones Aug 09 '17 at 19:59
  • 1
    No permission issues. Just need to be careful with php versions and paths, sometimes they are different from shell. alse exec() is often turned off on shared hosting.. – Harry Bosh Jul 06 '18 at 01:32
  • Updated for 5.6 see comments on the link for 5.1 – Harry Bosh Dec 09 '18 at 23:09
  • this use `queue:work` which means didn't listen any code changes, or at least need manually restart the process. but in the code i didnt see any restarting.. any idea? – david valentino Jan 16 '20 at 03:29
  • @davidvalentino you need to add to the kernel $schedule->command('queue:checkup')->everyFiveMinutes(); – Harry Bosh Jan 16 '20 at 23:45
  • @HarryBosh yes that to ensure to queue is running every 5 minutes, so if the old queue:work is active, it will never detect any code changes,, unless the pid is killed – david valentino Jan 17 '20 at 07:41
  • 1
    This has really saved my website! The supervisor which others were recommending was not available to me, and this method works very well. No issue. I did have to change the command slightly for my environment. – Forseth11 Dec 11 '20 at 19:11
7

1)sudo apt install supervisor or

sudo apt-get install supervisor

2)cd /etc/supervisor/conf.d 3)create new file inside

sudo vim queue-worker.conf

File Content

[program:email-queue]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/html/laravelproject/artisan queue:work
autostart=true
autorestart=true
user=root
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/html/laravelproject/storage/logs/supervisord.log

4)sudo supervisorctl reread

when run this command get output queue-worker:available

5)sudo supervisorctl update

when run this command get output queue-worker:added process group

other command

1)sudo supervisorctl reload

when run this command get output Restarted supervisord

2)sudo service supervisor restart

6

For those who are already running NodeJS on their production environments. I use PM2 to manage app processes.

# install
npm install -g pm2

# in project dir with your CI or dev setup tool 
# --name gives task a name so that you can later manage it
# -- delimits arguments that get passed to the script
pm2 start artisan --interpreter php --name queue-worker -- queue:work --daemon

I use Vagrant in development and setup NodeJS and this process using only inline vagrant scripts.

When you use PM2 in development you can use one of the many watchers to manage the restart. Simply run pm2 restart queue-worker when you pick up a change. In production I don't recommend this approach, rather opt for a build tool that can follow this process.

# 1. stop pm task to ensure that no unexpected behaviour occurs during build
pm2 stop queue-worker
# 2. do your build tasks
...
# 3. restart queue so that it loads the new code
pm2 restart queue-worker
dewwwald
  • 257
  • 3
  • 14
6

Installing Supervisor

sudo apt-get install supervisor

Configuring Supervisor

step 1 : goto /etc/supervisor/conf.d directory

cd /etc/supervisor/conf.d

step 2 : create a worker file laravel-worker.conf that will listen queue

sudo nano laravel-worker.conf

*Note : Now assuming that your laravel app is inside /var/www/html directory

project folder is : /var/www/html/LaravelApp

step 3 : paste below code in laravel-worker.conf and save file

[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/html/LaravelApp/artisan queue:listen redis --queue=default --sleep=3 --tries=3 
autostart=true
autorestart=true
user=root
numprocs=8
redirect_stderr=true
stdout_logfile= /var/www/html/LaravelApp/storage/logs/worker.log

*Note : Here is assumed that you are using redis for queue connection

in .env file QUEUE_CONNECTION=redis

command=php /var/www/html/LaravelApp/artisan queue:listen redis

if you are using other connection then , general syntax will be :

command= php [project_folder_path]/artisan queue:listen [connection_name]

[connection_name] can be any of sync , database , beanstalkd , sqs , redis

step 4 : create a worker file laravel-schedule.conf that will run artisan schedule:run command at every 1 minute (60 seconds) (*you can change it as per your requirement)

[program:laravel-schedule]
process_name=%(program_name)s_%(process_num)02d
command=/bin/bash -c 'while true; do date && php /var/www/html/LaravelApp/artisan schedule:run; sleep 60; done'
autostart=true
autorestart=true
numprocs=1
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0

step 5 : Starting Supervisor : run below commands

sudo supervisorctl reread

sudo supervisorctl update

sudo supervisorctl start all

*Note : Whenever you make changes in any of configuration .conf files , run above commands of Step 5

Extra usefull information :

  • to stop all supervisorctl process : sudo supervisorctl stop all
  • to restart all supervisorctl process : sudo supervisorctl restart all

usefull links :

https://laravel.com/docs/5.8/queues#running-the-queue-worker

http://supervisord.org/index.html

Saurabh Mistry
  • 8,903
  • 4
  • 33
  • 51
4

Using pm2

I had JS script running with pm2 (Advanced, production process manager for Node.js) Which was the only one I was running. But now as I got one more process to keep running.

I created process.yml to run both with a single command. Check the first one would run php artisan queue: listen

# process.yml at /var/www/ which is root dir of the project
apps:
  # Run php artisan queue:listen to execute queue job
  - script    : 'artisan'
    name      : 'artisan-queue-listen'
    cwd       : '/var/www/'
    args      : 'queue:listen' # or queue:work
    interpreter : 'php'

  # same way add any other script if any.

Now run:

> sudo pm2 start process.yml

Check more options and feature of pm2

Lahar Shah
  • 5,402
  • 4
  • 20
  • 37
4

Since this was a Laravel-specific question, I thought I would suggest a Lravel-specific answer. Since you are already using cronjobs on this server, I would recommend that you set up the shell command as a recurring cronjob to always verify that the worker is running. You could either set up the shell command to run natively through cron on your server, or you could use the Laravel console kernel to manage the command and add logic, such as checking whether you already have a worker running and, if not, go ahead and start it back up.

Depending on how often you need to run your command, you could do this as infrequently as once a week, or even once a minute. This would give you the ability to make sure that your workers are continuously running, without having to add any overhead to your server, such as Supervisor. Giving permissions to a 3rd party package like supervisor is ok if you trust it, but if you can avoid needing to rely on it, you may want to consider this approach instead.

An example of using this to do what you want would be to have a cronjob that runs each hour. It would execute the following in sequential order from within a custom Laravel console command:

\Artisan::call('queue:restart');

\Artisan::call('queue:work --daemon');

Note that this applies for older versions of Laravel (up to 5.3) but I haven't tested on newer versions.

eResourcesInc
  • 710
  • 5
  • 13
  • Although this is one option and would work, in the worst case this would result in an hour of downtime for the queued tasks. Also, this looks like it would create a new process each time the cron runs, if so eventually you would run out of memory. – zeros-and-ones Aug 21 '18 at 17:00
  • An hour of downtime? I just meant that the two artisan commands should be run in sequential order within the cronjob, not an hour apart. I will update the original answer to reflect this. This would not cause a memory issue since the restart command kills the previous process. – eResourcesInc Aug 22 '18 at 17:19
  • Regarding potential hour of downtime, we had a problem on our server where the artisan daemon worker would end up killed off for unknown reasons and would not stay alive. Supervisor was the only way to ensure it was revived at death. – zeros-and-ones Aug 23 '18 at 18:03
  • The above code should also work. You could call these commands as often as you want, as often as every minute. So I don't understand the comment about having an hour of downtime using this method. You have control over how often you check and restart the daemon. The whole point in posting this was just to give an example of how to do it using only Laravel. There are definitely other ways to do it. But this way does not rely on an external package to be installed. – eResourcesInc Aug 27 '18 at 18:16
  • Fair enough, you got my upvote. Interested to see if anyone has any comments on why not just do it this way vs using process monitor. – zeros-and-ones Aug 27 '18 at 21:17
  • 1
    I think that most people don't mind installing a process monitor, but others might not want to give that level of access to an external package that they don't control. It's personal preference. The service monitor is probably cleaner if you don't mind doing that type of install on the server, but this method achieves the same without any additional external dependencies and should be platform agnostic. But both have plusses and minuses. – eResourcesInc Aug 28 '18 at 18:42
  • +1 This is the 'PHP way' to do it, efficient for shared hosting platforms where no custom programs (such as supervisor) are allowed to be installed. – user8555937 Dec 04 '19 at 12:44
4

The best way is PM2 (Advanced, production process manager for Node.js) that you can monit your queues and see their's logs.

with command below in your project directory, run queue worker :

pm2 start artisan --name laravel-worker --interpreter php -- queue:work --daemon
faridcs
  • 402
  • 1
  • 4
  • 12
2

What if you start the listening within a screen? See here: http://aperiodic.net/screen/quick_reference Then even if you log out, the screen would still be active and running. Not sure why the daemonization doesnt work though.

grasshopper
  • 3,621
  • 3
  • 20
  • 28
2

For systems with systemd as init service you could use the following service, adapting it to your project (create it on /etc/systemd/system/queue-handler.service):

[Unit]
Description = Queue Handler - Project
After = network-online.target, mysql.service

[Service]
User = www-data
Type = simple
WorkingDirectory=/var/www/project
ExecStart = /usr/bin/php /var/www/project/artisan queue:work --tries=3
Restart = on-failure
RestartSec=5s
RestartPreventExitStatus = 255

[Install]
WantedBy = multi-user.target

Reload the configurations and enable it on boot:

$ systemctl enable queue-handler.service
$ systemctl daemon-reload
SnakeDrak
  • 2,527
  • 2
  • 21
  • 37
1

You can use monit tool. it's very small and useful for any type of process management and monitoring.

After Downloading binary package from this link, you can extract it to a folder on your system and then copy two files from the package to your system to install it:

cd /path/to/monit/folder
cp ./bin/monit /usr/sbin/monit
cp ./conf/monitrc /etc/monitrc  

Now edit /etc/monitrc base on your needs(reference doc). then create a init control file to enable monit on startup. now start monit like this:

initctl reload-configuration
start monit
Ghasem Pahlavan
  • 581
  • 6
  • 19
1

For CentOS7

yum install supervisor

Then create a file in /etc/supervisord.d/filename.ini With content

[program:laravel-worker]
command=/usr/bin/php /home/appuser/public_html/artisan queue:listen
process_name=%(program_name)s_%(process_num)02d
numprocs=5
priority=999
autostart=true
autorestart=true
startsecs=1
startretries=3
user=appuser
redirect_stderr=true
stdout_logfile=/path/logpath/artisan.log

Then start the supervisord service using

systemctl restart supervisord

Enable supervisord service to run on boot using

systemctl enable supervisord

Check if the service is running using

ps aux | grep artisan

You should see the process running if it was set up properly. Similar to the output below.

[root@server ~]# ps aux | grep artisan
appuser 17444  0.1  0.8 378656 31068 ?        S    12:43   0:05 /usr/bin/php /home/appuser/public_html/artisan queue:listen
Siru
  • 51
  • 4
0

You can also use Docker containers.

checkout:

Amin Shojaei
  • 1,943
  • 1
  • 14
  • 26
0

I achieved the result without any service monitor or third party software. The solution is working fine but I am not sure if it is the best way.

Solution

Just run cli command in the following way in your function.

use Illuminate\Console\Command;

public function callQueue()
{
        $restart = 'php-cli ' . base_path() . '/artisan queue:restart > /dev/null & echo $!'; 
        $work = 'php-cli ' . base_path() . '/artisan queue:work --timeout=0 --sleep=5 --tries=3 > /dev/null & echo $!';
        exec($restart);
        exec($work);
}

$job = (new jobName())->delay(Carbon::now()->addSeconds(5));
dispatch($job);

Reason

The reason I have used these two commands is because the command associated with $restart prevent having any memory issue according to a comment in this answer and the command associated with $work ensures that the command is successfully executed before the job.

Riwaj Chalise
  • 425
  • 5
  • 15
-2

I simply used php artisan queue:work --tries=3 & which keeps the process running in the background. But it sometimes stops. I don't know why this is happening

Edit

I solved this issue by using supervisor. Put a supervisor script that runs this php script, and that will run every time the server runs

Jithin
  • 2,487
  • 18
  • 36