Laravel Tutorials

Laravel queue:work Now Prints Why the Worker Stopped

Published
Laravel queue:work Now Prints Why the Worker Stopped image

A queue worker exits and Supervisor starts a new one. The log shows jobs processing, then nothing, then jobs processing again. Whether that gap was a clean queue:restart, a memory limit, or a lost database connection, the output looked the same.

Laravel 13.30 has queue:work write the reason on the way out:

2026-09-01 13:20:40 Worker STOPPED Memory limit exceeded

What Was Already There

The worker has known why it was stopping for a while. WorkerStopping carries a WorkerStopReason along with the exit status, the number of jobs processed, the timestamp of the last job, and the memory in use at the moment it quit:

use Illuminate\Queue\Events\WorkerStopping;
 
Event::listen(function (WorkerStopping $event) {
Log::info('Worker stopped', [
'reason' => $event->reason?->value,
'status' => $event->status,
'jobs' => $event->jobsProcessed,
'memory' => $event->memoryUsage,
]);
});

That works, and it is still the right tool when the destination is a metrics backend. It is a lot of ceremony for the common case, which is wanting to see in a terminal or a log why the process you were watching went away.

The Output

queue:work now listens for the event itself and writes a final line. In a terminal:

2026-09-01 13:20:40 Worker STOPPED Queue empty

With --json, it goes out as a record alongside the per-job output, so a log pipeline that already parses one parses the other:

{"level":"info","status":"stopped","reason":"empty","exit_code":0,"jobs_processed":12,"memory":34.0,"timestamp":"2026-09-01T13:20:40.118273+00:00"}

reason is the enum's backing value, stable to match on. memory is megabytes, rounded to one decimal. level follows the exit code: info for a clean exit, warning for anything else, which in practice means the memory limit and a job timeout.

Nothing is written under --quiet or --silent, or when the reason is null. That last case is worth knowing: a worker killed from outside, by an OOM killer or a SIGKILL, never reaches the code that dispatches the event. Silence still means the process died without being asked to.

The Nine Reasons

WorkerStopReason gained a description() method holding the human-readable strings, so the enum is now the single place both the console output and your own listeners can read them from:

Value Description Exit code
empty Queue empty 0
empty_for Queue empty for the configured duration 0
max_jobs Maximum jobs exceeded 0
max_time Maximum run time exceeded 0
restart_signal Received restart signal 0
interrupted Interrupted 0
lost_connection Lost connection 0
memory Memory limit exceeded 12
timed_out Job timed out 1

The two non-zero codes are the ones to alert on, and they mean different things:

memory is the worker noticing its own memory usage passed --memory between jobs, which is the mechanism working as designed. It becomes a signal when it happens after two jobs instead of two thousand, because that points at a job holding a large result set or a leak in something the worker loads once. Exit code 12 is Worker::EXIT_MEMORY_LIMIT, and it is configurable through Worker::$memoryExceededExitCode if your process manager treats specific codes differently.

timed_out is different in kind. The other eight are the worker deciding to stop between jobs; this one is the parent process killing a child that ran past --timeout mid-job. That job's failed() may not have run the way it would for an ordinary failure, so a run of timed_out exits usually means one job needs a longer $timeout or a smaller unit of work.

The zero-status reasons are mostly informational, with one exception. lost_connection exits cleanly because reconnecting is the process manager's job, but a steady stream of them says the database or Redis connection is being dropped underneath the worker, often by an idle timeout on a firewall or proxy rather than by the database itself.

The rest describe intent: restart_signal is someone running queue:restart, typically a deploy. empty and empty_for are --stop-when-empty and --stop-when-empty-for doing their job, expected on a container that scales to zero. max_jobs and max_time are --max-jobs and --max-time, the deliberate recycle that keeps a long-lived PHP process from accumulating state. interrupted is a SIGINT or SIGTERM, which is what Ctrl-C and most container shutdowns send.

Reading It in Production

The value of the JSON format is that "why did the worker restart" stops being a question you answer by correlating timestamps. Filter on the reason and the answer is in one field:

php artisan queue:work --json --max-time=3600 2>&1 | \
jq -c 'select(.status == "stopped")'

Under a process manager writing to a file, the same query runs against the log after the fact. A worker recycling on max_time every hour is the configuration working. The same worker recycling on memory every four minutes is a leak, and previously both looked like a restart.

The event listener is still there for anything beyond reading, such as incrementing a counter per reason so a dashboard shows the mix over time rather than one line per exit.

Contributed by @jackbayliss in #61339.

Paul Redmond photo

Staff writer at Laravel News. Full stack web developer and author.

Sponsored

serpapi logo
SerpApi

The Web Search API for Your LLM and AI Applications

Visit SerpApi

The latest

View all →
Group Adjacent Collection Items in Laravel with chunkBy() image

Group Adjacent Collection Items in Laravel with chunkBy()

Read article
Sidecar Brings Statamic's Control Panel to Your Existing Markdown Sites image

Sidecar Brings Statamic's Control Panel to Your Existing Markdown Sites

Read article
Collections chunkBy() and Storage Path Hardening in Laravel 13.30 image

Collections chunkBy() and Storage Path Hardening in Laravel 13.30

Read article
Forte: Parse and Rewrite Laravel Blade Templates image

Forte: Parse and Rewrite Laravel Blade Templates

Read article
Compoships: Eloquent Relationships on Multiple Columns image

Compoships: Eloquent Relationships on Multiple Columns

Read article
MKSine: A Filament CMS with Plugins, Themes, and Blocks image

MKSine: A Filament CMS with Plugins, Themes, and Blocks

Read article