Diagnosing Laravel Queue Worker Failures: A Step‑by‑Step Guide
When jobs pile up in the database and workers vanish from the process list, pinpointing the root cause can be tricky. This guide walks you through the most common symptoms, a diagnostic matrix, ordered checks, targeted fixes, and when to involve the ops team.
02 Dec 2025, 22:48 UTC

Recognizable Symptom
In a production environment you notice that the jobs table keeps growing, reserved_at never changes, and the php artisan queue:work process is missing from ps aux or Supervisor reports it as FATAL/EXITED. No entries appear in failed_jobs, and the application logs show no worker‑specific errors.
Cause / Diagnostic Matrix
| Possible Cause | Diagnostic Check |
|---|---|
| Worker process not running | ps aux | grep queue | grep -v grep |
| Wrong QUEUE_CONNECTION or queue name | cat .env | grep QUEUE_CONNECTION |
| Uncaught exception in job code | tail -f storage/logs/laravel.log | grep "Exception" |
| Low timeout or memory flag | ps aux -o pid,cmd | grep queue:work |
| Config cached before env vars set | php artisan config:cache | grep -i queue |
| Database queue table locked | SHOW ENGINE INNODB STATUS | grep "Locked" |
| Horizon misconfigured (Redis only) | php artisan horizon:status |
Ordered Checks
- Confirm worker presence
ps aux | grep '[q]uery:work'If absent, check Supervisor or systemd status:
sudo supervisorctl status laravel-worker:* - Verify queue configuration
cat .env | grep QUEUE_CONNECTION php artisan config:show queue.connections | grep -i defaultEnsure
QUEUE_CONNECTIONin.envmatches the driver used by your jobs. - Inspect job table state
mysql -u user -p -e "SELECT id, reserved_at, attempts FROM jobs ORDER BY id DESC LIMIT 5;"Rows with
reserved_atnull and attempts > 0 indicate a stalled worker. - Check logs for worker exceptions
tail -n 1000 storage/logs/laravel.log | grep "queue"Look for stack traces that were not routed to
failed_jobs. - Reproduce a single job
php artisan queue:work --once -vvvUse
--onceonly for debugging; it exits after one job. - Validate config cache
php artisan config:clear php artisan config:cacheAfter clearing, re‑run
queue:workand monitorreserved_atchanges. - Test Horizon (if used)
php artisan horizon:statusEnsure Redis is reachable and the Horizon supervisor is active.
Tied Fixes
- Restart worker
If Supervisor reports
EXITED, run:sudo supervisorctl restart laravel-worker:*For systemd:
sudo systemctl restart laravel-worker@.service. - Correct queue name / driver
Update
.envand runphp artisan config:clear. - Increase timeout / memory
Example:
php artisan queue:work redis --timeout=300 --memory=256.
In Supervisor config, setcommand=php /path/to/artisan queue:work redis --timeout=300 --memory=256. - Address uncaught exceptions
Wrap job logic in
try/catch, log the error, and re‑throw if needed, or usedispatchNow()during debugging. - Unlock database queue
If InnoDB locks persist, run
FLUSH TABLES WITH READ LOCKcarefully or restart MySQL after ensuring no active transactions. - Re‑create queue table
When the
jobstable is missing columns, runphp artisan queue:tableandphp artisan migrate. - Use afterCommit for DB‑transactional jobs
Dispatch jobs inside
DB::transaction(function() { ... });withdispatchAfterCommit()to avoid them running before the transaction is committed.
Escalation Criteria
- Workers killed by OOM or SIGSEGV
Check
/var/log/syslogorjournalctl -xeforOut of memory: Kill processentries. - Redis connection drops under load
Monitor Redis
CLIENT LISTandMONITORlogs for frequent disconnections. - High job failure rate with no
failed_jobsentries
Indicates jobs are being silently swallowed; review
app/Exceptions/Handler.phpforreportoverrides. - Persistent lock contention on MySQL
Consider moving to Redis or enabling
queue:retrywith back‑off strategy. - Cross‑server rate limiting required
Use
Illuminate\Support\Facades\RateLimiteror a distributed lock likeredlockif jobs need to be throttled across multiple workers.
Practical Verification
After applying a fix, dispatch a test job from Tinker:
php artisan tinker
>>> dispatch(new App\\Jobs\\SendEmailJob($userId));
Then run:
mysql -u user -p -e "SELECT id, reserved_at, attempts FROM jobs WHERE id = LAST_INSERT_ID();"
Confirm reserved_at updates within a minute. Finally, run php artisan queue:failed --status=failed to ensure no new failures appear.
Limitations & Caveats
- queue:work does not reload code changes – after editing job classes, restart workers.
- Config caching can hide .env values – avoid
env()calls in queue config unless you clear the cache. - Horizon is Redis‑only – do not deploy it if you rely on the database driver.
- --once is for debugging only – never use it in production, as it exits after one job.
- Database queues suffer lock contention – for high throughput, prefer Redis or SQS.
Conclusion
By following the ordered checks and applying the targeted fixes, most Laravel queue worker failures can be resolved without a full outage. If the problem persists after the escalation criteria are met, involve your operations or DevOps team to review infrastructure limits, network latency, or external service dependencies.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.