In Laravel, what is the difference between php artisan queue:work and php artisan queue:listen, and which belongs in production?
answer
- boot once versus boot per job
- listen spawns queue:work --once
- work needs a restart after deploys
- php artisan dev runs queue:listen
- defaults: --tries=1, --timeout=60, --memory=128
basics
~20 squeue: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# 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-emptygo deeper
Recall that queue:work is the long-lived worker for production and queue:listen reboots per job for development.
Explain the daemon loop, the --once children behind queue:listen, and the defaults a bare queue:work runs with.
Tie the process model to operations: restarts on deploy, recycling with --max-jobs or --max-time, and state that survives between jobs.
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