Monday, May 11, 2026

Laravel Queue Workers Stuck on Docker: Why My Queue Fails to Process and How to Fix It in Minutes

Laravel Queue Workers Stuck on Docker: Why My Queue Fails to Process and How to Fix It in Minutes

You’ve spent hours watching php artisan queue:work spin forever while your jobs pile up in Redis. The logs are silent, Docker containers look healthy, but the queue never moves. It’s the kind of developer nightmare that makes you question every line of your docker-compose.yml. In this article we’ll cut through the noise, pinpoint the common culprits, and give you a bullet‑proof, production‑ready fix that works on a Laravel‑Docker stack running on a VPS or even a shared host.

Why This Matters

Stalled queue workers mean delayed emails, missed webhook callbacks, and a poor API experience. In a SaaS environment a single stuck queue can cascade into lost revenue and angry customers. Moreover, idle workers waste CPU cycles and inflate your cloud bill—something every PHP optimization enthusiast fights against.

Common Causes

  • Incorrect QUEUE_CONNECTION environment variable (Redis vs. database).
  • Docker container not sharing the same network as the Redis service.
  • Supervisor/Process manager misconfiguration causing workers to exit silently.
  • Insufficient php-fpm workers or low memory_limit causing OOM kills.
  • File permission issues on storage/framework/cache that prevent job payload serialization.
  • Missing pcntl extension in the PHP image.

Step‑By‑Step Fix Tutorial

1. Verify Docker Network Connectivity

# Inspect the Docker network
docker network ls

# Ping Redis from the app container
docker exec -it laravel_app ping redis
INFO: If the ping fails, recreate the network and attach both services:
docker network create laravel_network
docker-compose up -d

2. Ensure Correct Queue Driver

# .env
QUEUE_CONNECTION=redis
REDIS_HOST=redis
REDIS_PORT=6379

3. Install PCNTL Extension

# Dockerfile (PHP 8.x)
FROM php:8.2-fpm

RUN apt-get update && apt-get install -y \
    libzip-dev zip unzip git \
    && docker-php-ext-install pdo_mysql zip pcntl \
    && pecl install redis && docker-php-ext-enable redis
TIP: Rebuild the image with docker compose build --no-cache to avoid cached layers.

4. Configure Supervisor Properly

# /etc/supervisor/conf.d/laravel-queue.conf
[program:laravel-queue]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/html/artisan queue:work redis --sleep=3 --tries=3 --daemon
autostart=true
autorestart=true
user=www-data
numprocs=4
redirect_stderr=true
stdout_logfile=/var/log/laravel/queue-worker.log
stopwaitsecs=3600
# Reload supervisor inside container
supervisorctl reread
supervisorctl update
supervisorctl status
WARNING: Do not use --daemon with Laravel 9+; the flag is deprecated. Use --once in a cron or let Supervisor manage the process lifecycle.

5. Tune PHP‑FPM & MySQL

# /usr/local/etc/php-fpm.d/www.conf
pm = dynamic
pm.max_children = 20
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 8
php_admin_value[memory_limit] = 256M
# MySQL my.cnf (on Ubuntu VPS)
[mysqld]
innodb_buffer_pool_size = 1G
max_connections = 250
query_cache_type = 0

VPS or Shared Hosting Optimization Tips

  • On a VPS, allocate at least 2 vCPU and 4 GB RAM for a busy Laravel queue.
  • If you’re on shared hosting, switch from Redis to the database driver to avoid socket permission issues.
  • Enable opcache in php.ini for faster job deserialization.
  • Use Cloudflare “Cache‑Everything” for static assets; it reduces Nginx load.

Real World Production Example

Acme SaaS runs 12 million queued emails per month on a 2‑node Docker Swarm. Their original setup stalled during peak traffic because the Redis service hit a max‑clients limit. The fix:

# redis.conf
maxclients 10000
tcp-backlog 511

After raising maxclients and adding a second Redis replica, queue latency dropped from 45 seconds to under 2 seconds.

Before vs After Results

Metric Before Fix After Fix
Avg. Job Latency 45 s 1.8 s
CPU Usage (app container) 85 % 38 %
Memory (Redis) 1.2 GB 750 MB

Security Considerations

  • Never expose Redis port 6379 to the public internet; keep it in an internal Docker network.
  • Use APP_KEY generated with php artisan key:generate and rotate every 90 days.
  • Enable sudo‑less user for Docker (e.g., www-data) to limit container breakout.
  • Set daemonize no in redis.conf and run under a non‑root user.

Bonus Performance Tips

SUCCESS: Adding a dedicated queue Docker service with 2 vCPU and 2 GB RAM reduced job processing time by 60 % without touching the main app container.
  • Leverage Laravel Horizon for real‑time worker monitoring and auto‑scaling.
  • Use php artisan queue:restart after each deployment to avoid stale code.
  • Batch large jobs (e.g., chunk(100)) to limit memory usage.
  • Set retry_after in config/queue.php to a value higher than average job runtime.

FAQ

Q: My queue works locally but not in Docker.
A: Check REDIS_HOST. In Docker it should be the service name, not 127.0.0.1. Also verify the network mode is bridge or a custom network shared by all services.
Q: Should I use queue:work --daemon or queue:listen?
A: For Laravel 9+ the recommended approach is queue:work managed by Supervisor. queue:listen is deprecated and adds extra overhead.

Final Thoughts

Stuck queue workers are rarely a mystery; they’re usually a combination of networking, missing extensions, and mis‑tuned process managers. By following the steps above you can get your Docker‑based Laravel queues humming again in under ten minutes, save CPU cycles, and keep your API customers happy.

Ready to level up your hosting? Cheap secure hosting from Hostinger offers one‑click Docker, Alpine‑based PHP images, and a 99.9 % SLA—perfect for Laravel SaaS on a budget.

No comments:

Post a Comment