Fix laravel queue workers stuck after deploy

Avatar
M

Maksudur Rahman

Software Engineer•
3Views
5mRead
0Reactions

The queue workers hang in a "processing" state after a code deploy because the PHP processes managed by Supervisor retain a stale OpCache that still references the previous .env and class map. Running php artisan queue:work --once reproduces the lock, and a simple php artisan cache:clear && supervisorctl restart all resolves it. The fix is to force OpCache invalidation on each deploy and adjust the Supervisor config to restart workers automatically.

TL;DR / Quick Summary

  • Verify workers are stuck with php artisan queue:work --once.

  • Check Supervisor logs for SIGTERM or OpCache warnings.

  • Add opcache.validate_timestamps=1 and opcache.revalidate_freq=0 to PHP or prepend -d opcache.enable=0 to the worker command.

  • Restart workers via supervisorctl restart laravel-queue-worker:* or run php artisan queue:restart after each deploy.

  • Use the ready‑to‑paste Supervisor config snippet below.

Reproducing the Stuck State

1. Run a single‑job worker

php artisan queue:work --once

If the command exits with Processing and never returns, the worker is stuck.

2. Simulate a deploy

git pull origin main
composer install --no-dev
php artisan config:cache
php artisan route:cache
# Do NOT restart workers yet

After the deploy, the existing workers keep the old OpCache and remain in the processing loop.

Diagnosing with Supervisor Logs

Supervisor writes to /var/log/supervisor/laravel-queue-worker.log (path defined in the config). Look for lines such as:

2026-11-05 12:34:56,789 INFO spawned: 'laravel-queue-worker_00' with pid 12345
2026-11-05 12:35:01,102 WARN No job available; worker sleeping 3 seconds
2026-11-05 12:35:01,102 INFO exited: laravel-queue-worker_00 (exit status 0; not expected)

A missing SIGTERM restart indicates the process never received the queue:restart signal.

Root Cause: OpCache Stale after .env Change

PHP 8.3 ships with OpCache enabled by default. When a deploy updates environment variables or compiled class files, the running PHP processes keep the old bytecode until the cache expires. Because Laravel’s queue workers are long‑running, they never see the new configuration, causing them to stay in a phantom "processing" state.

Verified Fixes

1. Force OpCache Invalidation on Deploy

Add the following to your deployment script after composer install:

php artisan config:clear
php artisan cache:clear
# Invalidate OpCache for the current CLI process
php -r "opcache_reset();"
php artisan queue:restart

opcache_reset() clears the shared memory cache for the current PHP binary, ensuring the next worker spawn loads fresh code.

2. Adjust PHP Settings (php.ini or .user.ini)

; php.ini (PHP 8.3)
opcache.validate_timestamps=1
opcache.revalidate_freq=0

These settings make OpCache check file timestamps on every request, eliminating the need for a manual reset.

3. Update Supervisor Worker Command

Create or edit ./supervisor/conf.d/laravel-worker.conf:

[program:laravel-queue-worker]
process_name=%(program_name)s_%(process_num)02d
command=php -d opcache.enable=0 /var/www/html/artisan queue:work redis --sleep=3 --tries=3 --timeout=60
autostart=true
autorestart=true
user=www-data
numprocs=4
redirect_stderr=true
stdout_logfile=/var/log/supervisor/laravel-queue-worker.log
stopwaitsecs=360

Setting -d opcache.enable=0 disables OpCache for the worker process only, guaranteeing fresh code after each deploy while keeping OpCache enabled for the web‑request PHP-FPM pool.

4. Restart Supervisor

supervisorctl reread
supervisorctl update
supervisorctl restart laravel-queue-worker:*

All workers now start with a clean OpCache and will pick up new jobs immediately.

Comparison Table

Approach

OpCache Impact

Deploy Overhead

Recommended For

php artisan queue:restart only

Uses existing OpCache, may stay stale

Minimal

Small teams, infrequent env changes

opcache.validate_timestamps=1

Real‑time validation

No extra steps

Production with frequent config changes

Disable OpCache in worker command

No OpCache for workers

Slight CPU increase

Critical latency jobs, hot deploys

Common Pitfalls & Troubleshooting

  • Workers still show "processing" after restart – Verify that the Supervisor config points to the correct artisan path and that the php binary used matches the one where you changed opcache settings.

  • Memory leak warnings (php 8.3 queue worker memory leak) – Add --memory=128 to the worker command or enable Laravel’s --max-jobs=1000 to force periodic restarts.

  • Redis connection errors – Ensure REDIS_HOST and REDIS_PASSWORD are refreshed after a deploy; run php artisan config:clear before restarting workers.

  • Supervisor not picking up config changes – Always run supervisorctl reread && supervisorctl update after editing .conf files.

FAQ

  • Why does php artisan queue:work --once hang after a deploy? Because the long‑running worker process keeps a stale OpCache that still references the old environment and class map.

  • Do I need to disable OpCache globally? No. You can limit the change to the worker command or adjust validation settings to keep OpCache for web requests.

  • Will php artisan queue:restart alone fix the issue? It works only if OpCache validation is enabled; otherwise the workers may still use cached bytecode.

  • How can I detect this problem in CI? Add a step that runs php artisan queue:work --once after a simulated deploy and asserts a non‑zero exit code.

Related Engineering Guides

Recommended Resources & Courses

Partner Links

React to this article