skip to content

Worker Processes & Backends

Workers read config/queue.php connections such as database, redis and sqs, and queue:work flags cap tries, timeout, memory and lifetime. Interviewers probe retry_after, stale code and Supervisor.

on this pageshow

explore

questions

5

In Laravel, what is the difference between php artisan queue:work and php artisan queue:listen, and which belongs in production?

level: juniorimportance: must knowfreq 62%

answer

  1. boot once versus boot per job
  2. listen spawns queue:work --once
  3. work needs a restart after deploys
  4. php artisan dev runs queue:listen
  5. defaults: --tries=1, --timeout=60, --memory=128

basics

~20 s

queue:work boots the app once and runs jobs in one long-lived process: fast, but blind to new code until restarted. queue:listen spawns a fresh queue:work --once per job: current code, much slower. Production runs queue:work.

solid answer

~40 s

`php artisan queue:work` is a **daemon**: it boots the framework once, then polls the connection and runs jobs in the same process until it is stopped, reaches `--max-jobs`, `--max-time` or `--memory`, or sees a `queue:restart` signal. Because the booted application stays in memory, it is efficient, but it does not notice code changes and static state survives between jobs. `php artisan queue:listen` is a supervisor loop that launches `queue:work --once` as a child process for each iteration, so every job gets a freshly booted app and your latest code — which is why Laravel 13's `php artisan dev` runs `queue:listen --tries=1 --timeout=0`. That re-boot makes it much slower. Production runs `queue:work` under a process manager such as Supervisor and restarts it on deploy.

code

bash · 8 lines
bash
# Production: one long-lived worker per process slot
php artisan queue:work redis --queue=transcode,default --max-time=3600

# Local development: fresh boot for every job
php artisan queue:listen

# Container job: drain the queue, then exit
php artisan queue:work --stop-when-empty

go deeper

for a junior

Recall that queue:work is the long-lived worker for production and queue:listen reboots per job for development.

for a middle

Explain the daemon loop, the --once children behind queue:listen, and the defaults a bare queue:work runs with.

for a senior

Tie the process model to operations: restarts on deploy, recycling with --max-jobs or --max-time, and state that survives between jobs.

for a principal

Decide how worker processes are run and supervised across environments, weighing throughput against the cost of stale code and leaked state.

## Two commands, two process models A **queue worker** is the process that takes jobs off a queue connection and runs them. Laravel ships two Artisan commands for it, and they differ in how often the framework is booted. | | `queue:work` | `queue:listen` | |---|---|---| | Process model | one long-lived PHP process | a parent that spawns `queue:work --once` children | | Framework boot | once, at start | once per child, so once per job or poll | | Sees code changes | only after a restart | on the next job | | Speed | fast | much slower | | Typical use | production, under a process manager | local development | ## How queue:work runs `php artisan queue:work [connection]` builds an `Illuminate\Queue\Worker` and enters its daemon loop: 1. check whether it should stop — memory, max jobs, max time, a restart or pause signal; 2. pop the next job from the queues it was given (`--queue`), or the connection's default queue; 3. run the job, with a timeout alarm if the `pcntl` extension is loaded; 4. if there was no job, sleep for `--sleep` seconds (default 3) and loop. Because the container, config, service providers and any static properties stay in memory, the second job starts in microseconds rather than paying for a full boot. The docs spell out the price: workers "store the booted application state in memory", do not notice code changes, and do not reset static state between jobs. Defaults from the command's signature in Laravel 13: `--tries=1`, `--backoff=0`, `--timeout=60`, `--sleep=3`, `--memory=128`, `--max-jobs=0` and `--max-time=0` (0 meaning no limit). ## How queue:listen runs `php artisan queue:listen` accepts a similar set of options but never runs a job itself. The `Illuminate\Queue\Listener` builds a command line — `artisan queue:work <connection> --once --queue=... --memory=... --timeout=...` — and runs it as a Symfony `Process`, again and again. Each child boots the app from scratch, processes at most one job and exits. Consequences: - edits to a job class take effect on the next job with no restart; - no state leaks from one job to the next; - every job pays the full boot cost, so throughput is far lower; - the timeout is enforced on the child process by the parent, not by a signal inside it. That is why Laravel 13's `php artisan dev` (and the skeleton's `composer run dev` script that calls it) starts the queue as `queue:listen --tries=1 --timeout=0`: while you are editing code, correctness of what runs matters more than speed. ## When a queue:work process stops A daemon worker checks a list of stop conditions at the top of every loop — after each job and after each idle poll — and exits cleanly when one holds: - it lost its database or queue connection; - it received `SIGTERM`, `SIGINT` or `SIGQUIT` (with `pcntl`), in which case it finishes the job in hand first; - its allocated memory reached `--memory` megabytes, which exits with status 12; - `queue:restart` changed the restart timestamp in the cache; - `--max-jobs` or `--max-time` was reached, or the queue was empty with `--stop-when-empty`. A job that overruns its timeout is different: the alarm fires mid-job and the worker exits with an error status. In every case the process is gone, so production relies on a process manager to start another one. `queue:pause` is the exception that does not stop the process: a paused queue is skipped until `queue:continue`, while the worker keeps running. ## Useful flags for one-off runs - `--once` — process a single job and exit; the docs note that `--timeout` has no effect in this mode. - `--stop-when-empty` — drain the queue and exit, handy in a container that should stop when done. - `-v` — print job ids, connection and queue names as jobs are processed. - `--force` — keep processing while the app is in maintenance mode, which otherwise pauses workers. ## What production looks like In production you run several `queue:work` processes, each with explicit options, and something outside PHP keeps them alive: - a process manager such as Supervisor starts `numprocs` copies and restarts any that exit; - a deploy runs `php artisan queue:restart` so every worker finishes its current job and exits, and the manager starts fresh ones on the new code; - `--max-jobs` or `--max-time` recycle workers regularly, limiting slow memory growth. `queue:listen` in production is a common junior mistake: it hides the stale-code problem by paying a boot per job, which is exactly the cost `queue:work` exists to avoid.

  • Why can static state leak between jobs under queue:work but not under queue:listen?
    `queue:work` runs every job in one PHP process, so a static property or a singleton a job changes is still changed for the next job. `queue:listen` runs each job in a new child process that boots the app from scratch, so nothing carries over. Under `queue:work`, jobs must not rely on process-level state being fresh.
  • What does queue:work do while the queue is empty?
    It sleeps for `--sleep` seconds (3 by default) and polls again; on Redis, the connection's `block_for` option can make the pop wait for a job instead. Before each poll it checks its stop conditions, so a `queue:restart` signal is noticed within one sleep interval even when no jobs arrive.

saying these in an interview costs you the question

  • queue:listen is the faster command because it listens for events instead of polling
  • queue:work reloads changed PHP files automatically before each job
  • queue:work retries each failed job three times by default
  • queue:listen shares one booted application across all of its jobs
  • Running queue:work in a terminal is enough to keep workers alive in production
open as a page

After deploying a Laravel video-transcoding app, queue workers still run the old code; why, and how do queue:restart and Supervisor fix it?

level: middleimportance: must knowfreq 56%

basics

~20 s

queue:work loaded the old code at start and never reloads it. php artisan queue:restart writes a timestamp to the cache; each worker sees it, finishes its current job and exits, and Supervisor starts a fresh one.

open as a page

In Laravel, how do a queue connection's retry_after and queue:work --timeout interact, and how can a misconfiguration make one transcode job run twice?

level: seniorimportance: must knowfreq 50%

basics

~20 s

retry_after is how long a reserved job may run before the backend re-offers it; --timeout is how long the worker allows before killing itself. If a job outlives retry_after and --timeout is longer, a second worker runs it too.

open as a page

In Laravel 13's config/queue.php, which queue drivers ship, and when would you move off the default database driver?

level: middleimportance: should knowfreq 42%

basics

~20 s

The skeleton defines sync, database, beanstalkd, sqs, redis, deferred, background and failover connections, defaulting to database. Move to Redis or SQS when polling the jobs table loads the main database, throughput grows, or you want Horizon.

open as a page

How does Laravel's php artisan queue:work --queue=high,default prioritise jobs, and what can go wrong with that setup?

level: middleimportance: should knowfreq 38%

basics

~20 s

On every loop the worker checks the listed queues in order and takes the first job found, so default runs only when high is empty. A busy high queue starves the rest, and unlisted queues are never polled.

open as a page