You SSH in, run php artisan queue:work in a terminal, close the laptop, and go home. The next morning the queue is backed up with hundreds of jobs. The worker hit a fatal error at 2am and nothing brought it back. A queue worker is a long-running process, and long-running processes need a supervisor. Here's the exact Supervisor setup I put on every production box that runs plain database or SQS queues.
Install Supervisor on your server#
Supervisor is a process control system for Linux. It starts your worker on boot, watches the process, and restarts it the instant it exits — whether that exit came from a fatal error, an out-of-memory kill, or a reboot. On Ubuntu and Debian it's one apt package.
sudo apt-get update
sudo apt-get install -y supervisor
Once installed it runs as a system service. Confirm the daemon is up before you configure anything:
sudo systemctl status supervisor
Write the queue:work program config#
Supervisor reads [program:...] blocks from /etc/supervisor/conf.d/. Each block tells it what command to run and how to manage it. Create /etc/supervisor/conf.d/laravel-worker.conf and point command at your app's artisan binary with the full absolute path — Supervisor does not run through a login shell, so relative paths and $PATH lookups will bite you.
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/app/artisan queue:work database --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=4
redirect_stderr=true
stdout_logfile=/var/www/app/storage/logs/worker.log
stopwaitsecs=3600
The command here targets the database connection. Swap database for sqs if that's your driver. If you're structuring work with job chains or batches, the way you break jobs up matters as much as how many workers you run — I cover that trade-off in Laravel queue chains vs batches.
Tune worker count, restarts, and timeouts#
Three settings decide whether this config survives contact with production. numprocs is how many identical worker processes Supervisor runs in parallel — set it to how much concurrency your queue needs, not more than your CPU and database connections can feed. autorestart=true is the whole point: a worker that dies is immediately replaced. And stopwaitsecs is the one people get wrong.
stopwaitsecs is how long Supervisor waits after sending SIGTERM before it force-kills the process with SIGKILL. Laravel treats SIGTERM as "finish the current job, then exit cleanly." If stopwaitsecs is shorter than your longest-running job, Supervisor will hard-kill a worker mid-job and you lose it. Set it comfortably above your slowest job — and above the worker's own --timeout (default 60s). This is a different lever from throughput tuning like --max-jobs and --max-time, which control how long a worker lives before it recycles; I dig into that side in tuning Laravel Octane workers in production.
Reread the config and start the workers#
Supervisor doesn't watch the config directory. After adding or editing a .conf file you tell it to rescan, apply the changes, and start the new program group. reread picks up file changes, update reconciles running state with the config, and start launches the workers.
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start laravel-worker:*
The laravel-worker:* glob targets every process in the group, since numprocs=4 gives you laravel-worker:laravel-worker_00 through _03. Check they're all running:
sudo supervisorctl status
Restart workers on every deploy#
Queue workers are long-lived, so they hold your old code in memory until they're restarted. Deploy a bug fix and the workers keep running the buggy version until they recycle. The fix is php artisan queue:restart, which writes a timestamp to your cache; each worker checks that timestamp after finishing its current job and gracefully exits — Supervisor then restarts it with the fresh code.
php artisan queue:restart
Add that line to the end of your deploy script, after the code is in place and caches are rebuilt. It relies on a working cache driver, so make sure one is configured. If you deploy with Kamal or a similar tool, drop it into a post-deploy hook — I show where in deploying Laravel with Kamal 2 to a VPS. Pair it with zero-downtime migrations using expand and contract so a mid-deploy worker never hits a half-migrated schema.
Know when to reach for Horizon instead#
Plain Supervisor is the right tool for database and SQS queues, where you manage worker counts and timeouts yourself. If you're on Redis, Laravel Horizon is the better answer: it runs on top of Supervisor but gives you auto-balancing worker pools, a metrics dashboard, and failed-job insight out of the box — you configure it in config/horizon.php instead of hand-writing .conf files. The rule of thumb: Redis → Horizon, everything else → Supervisor. The same "keep it alive under a process manager" thinking applies to your scheduler and other daemons too — see running the Laravel scheduler in a Docker container with cron, and if you're hardening a box for production, automated database backups to S3 is the next thing I'd set up.
FAQ#
How do I keep a Laravel queue worker running?
Run php artisan queue:work under a process manager like Supervisor rather than by hand. Supervisor starts the worker on boot, restarts it automatically if it exits, and keeps a configurable number of worker processes alive. Without a process manager, the first fatal error, memory limit, or reboot stops your queue silently.
What is Supervisor and why do queue workers need it?
Supervisor is a Linux process control system that monitors and restarts long-running processes. Queue workers are long-lived by design — they boot the framework once and process jobs in a loop — so any crash takes the worker down until something restarts it. Supervisor is that something: it detects the exit and respawns the worker within seconds, so jobs keep flowing.
How many queue worker processes should I run?
Set numprocs to match the concurrency your queue actually needs, bounded by your CPU cores and available database or Redis connections. Start with a small number like 2–4, watch queue latency and server load, then scale up. Running far more workers than your resources can feed just adds contention without clearing the backlog faster.
How do I restart queue workers after a deploy?
Run php artisan queue:restart at the end of your deploy script. It signals every worker to finish its current job and exit gracefully, and Supervisor immediately restarts them with the new code. This is required because workers hold your application code in memory and won't pick up changes otherwise. The command depends on a configured cache driver to broadcast the restart signal.
Do I need Supervisor if I use Laravel Horizon?
No — Horizon manages its own worker processes and is itself the thing you keep alive under Supervisor (via a single horizon program block). If you're on Redis, use Horizon for its dashboard and auto-balancing. Plain Supervisor queue:work blocks are for database and SQS queues, where Horizon isn't available.