Laravel queue workers on shared hosting: always running via cron

Do you use Laravel queues for email, imports or other background tasks? Then you need a queue worker that processes the jobs. On a VPS you would usually use Supervisor for that. Supervisor is not available on our shared hosting, but you do not need it either. With a cron job and flock you keep a worker running continuously, and it starts up again on its own after a restart or outage.

How it works

Cron tries to start the worker every minute. flock makes sure that never more than one instance runs at the same time:

  • If the worker is already running, the new attempt stops immediately.
  • If the worker has stopped, for example after a server restart, a crash or a deploy, cron starts it again within a minute.
  • With --max-time the worker stops cleanly after a fixed time. That keeps memory usage low and means it always runs your latest code.

This gives you the same result as Supervisor, without extra software.

What do you need?

Step 1: choose the database as the queue

Set this in your .env:

QUEUE_CONNECTION=database

Since Laravel 11, the jobs table is included by default in a new project. Do you have an older project without that table? Create it via SSH:

php artisan make:queue-table
php artisan migrate

Tip: on our platform, Redis is meant as a cache. After a restart, recent data may be gone. So choose the database for jobs that must not be lost, such as email.

Step 2: create a folder for the lock files

Log in via SSH and run:

mkdir -p ~/locks

Step 3: create the cron job

In DirectAdmin, go to Cronjobs (see Setting up cron jobs in DirectAdmin). Enter a * in every field for minute, hour, day, month and weekday, so that the cron job runs every minute. Use this as the command:

/usr/bin/flock -n /home/GEBRUIKER/locks/worker1.lock /usr/local/bin/php /home/GEBRUIKER/PAD-NAAR-PROJECT/artisan queue:work --max-time=3600 --sleep=3 --tries=3 > /dev/null 2>&1

Replace GEBRUIKER with your DirectAdmin username and PAD-NAAR-PROJECT with the folder of your Laravel project, for example domains/jouwdomein.nl/laravel.

What each part does:

  • flock -n ...worker1.lock: only starts the worker if none is running yet.
  • /usr/local/bin/php: uses the PHP version you chose in DirectAdmin under Select PHP version.
  • --max-time=3600: the worker stops cleanly after an hour and cron immediately starts a new one.
  • --sleep=3: when the queue is empty, the worker checks every 3 seconds for new jobs.
  • --tries=3: a job that fails is tried at most 3 times and then ends up in the failed_jobs table.

Step 4: the scheduler (if you use it)

If you use the Laravel scheduler, add a second cron job that runs every minute:

/usr/local/bin/php /home/GEBRUIKER/PAD-NAAR-PROJECT/artisan schedule:run > /dev/null 2>&1

Do not also start the queue worker from the scheduler. The cron job from step 3 already takes care of that.

More than one worker

Need more processing capacity? Then add a second cron job with its own lock file:

/usr/bin/flock -n /home/GEBRUIKER/locks/worker2.lock /usr/local/bin/php /home/GEBRUIKER/PAD-NAAR-PROJECT/artisan queue:work --max-time=3600 --sleep=3 --tries=3 > /dev/null 2>&1

Each worker needs its own lock file. With the same lock, only one runs at a time. Want one worker per queue, for example for email? Then add --queue=mail.

After a deploy

Workers keep your code in memory. After uploading new code, run:

php artisan queue:restart

The workers finish their current job and stop. Within a minute, cron starts them again with the new code.

Checking that the worker is running

ps -u $USER -o pid,etime,cmd | grep "queue:work" | grep -v grep

If you see a line with artisan queue:work, the worker is running. You can view failed jobs with:

php artisan queue:failed

Good to know

  • A worker usually uses about 50 to 100 MB of memory. That counts toward the limits of your hosting plan.
  • Do not start a worker with nohup or &. A worker like that does not come back after a restart, and without flock you can easily start several by accident.
  • Only a few jobs? Then you can do without a permanent worker: let cron run queue:work --stop-when-empty every minute, also with flock. The worker then processes whatever is waiting and stops.
  • If you run a test environment and a production environment in separate hosting plans, each plan has its own cron jobs, workers and database.

Need help?

Stuck? Send us a message through the ticket system and we will take a look with you.

Looking for hosting for your Laravel app? View Laravel hosting.