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_CONNECTIONenvironment 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-fpmworkers or lowmemory_limitcausing OOM kills. - File permission issues on
storage/framework/cachethat prevent job payload serialization. - Missing
pcntlextension 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
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
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
--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
opcacheinphp.inifor 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
6379to the public internet; keep it in an internal Docker network. - Use
APP_KEYgenerated withphp artisan key:generateand rotate every 90 days. - Enable
sudo‑less user for Docker (e.g.,www-data) to limit container breakout. - Set
daemonize noinredis.confand run under a non‑root user.
Bonus Performance Tips
queue Docker service with 2 vCPU and 2 GB RAM reduced job processing time by 60 % without touching the main app container.
- Leverage
Laravel Horizonfor real‑time worker monitoring and auto‑scaling. - Use
php artisan queue:restartafter each deployment to avoid stale code. - Batch large jobs (e.g.,
chunk(100)) to limit memory usage. - Set
retry_afterinconfig/queue.phpto a value higher than average job runtime.
FAQ
Q: My queue works locally but not in Docker.
A: CheckREDIS_HOST. In Docker it should be the service name, not127.0.0.1. Also verify the network mode isbridgeor a custom network shared by all services.
Q: Should I usequeue:work --daemonorqueue:listen?
A: For Laravel 9+ the recommended approach isqueue:workmanaged by Supervisor.queue:listenis 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