Laravel Queue Workers Stuck Forever on cPanel VPS: How I Squashed the Bottleneck and Restored 30x Throughput in 3 Minutes
Ever stared at a Laravel queue that never finishes, while your API latency climbs like a roller‑coaster? I’ve been there—watching php artisan queue:work spin forever on a cPanel VPS, swallowing CPU, and killing revenue. In this post I’ll walk you through the exact root cause, the 5‑minute fix, and the extra tweaks that turned a sluggish 2 jobs/minute into a rock‑solid 60 jobs/minute.
Why This Matters
Queue workers are the heartbeat of any Laravel‑powered SaaS, WordPress‑integrated API, or e‑commerce site. When they hang:
- Orders stay pending.
- Emails never send.
- Customers experience timeouts, and you lose conversions.
In a production VPS, a single stuck worker can consume an entire CPU core, blowing up your PHP‑FPM pool and causing the whole site to lag. The fix not only restores performance; it safeguards your reputation.
Common Causes
- Mis‑configured Supervisor that never recycles stale processes.
- Low
php-fpmpm.max_childrenon a cPanel Apache environment. - Redis connection timeout or missing
phpredisextension. - Stale Composer autoload caches after a deployment.
- Ubuntu
swapexhaustion on low‑memory VPS.
INFO: The exact bottleneck in my case was a combination of Supervisor’s stopwaitsecs set to 9999 and a missing redis-cli ping health check. The result? Workers never died, and the queue kept queuing.
Step‑By‑Step Fix Tutorial
1️⃣ Verify the Queue State
# Check stuck jobs
php artisan queue:failed
php artisan queue:work --once --verbose
2️⃣ Update Supervisor Config
Open the Laravel worker configuration located at /etc/supervisord.d/laravel-worker.conf (or /etc/supervisor/conf.d/laravel-worker.conf on cPanel).
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /home/username/laravel/artisan queue:work redis --sleep=3 --tries=3 --timeout=90
autostart=true
autorestart=true
user=username
numprocs=8
redirect_stderr=true
stdout_logfile=/home/username/logs/worker.log
stopwaitsecs=30 ; <<< Reduce from 9999
startsecs=5
TIP: Keep numprocs equal to the number of CPU cores you actually have free after accounting for Nginx/Apache.
3️⃣ Tune PHP‑FPM Pool
Adjust the pool file (/opt/cpanel/ea-php74/root/etc/php-fpm.d/www.conf for PHP 7.4) to match your VPS memory.
pm = dynamic
pm.max_children = 30 ; 30 * 128M ≈ 3.8GB
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 15
pm.max_requests = 5000
4️⃣ Ensure Redis Health
Install phpredis and test the connection:
# Install extension
yum install php-pecl-redis -y
# Test
php -r "echo extension_loaded('redis') ? 'OK' : 'Missing';"
redis-cli ping
5️⃣ Clear Composer & Cache
# Remove stale autoload files
composer dump-autoload -o
# Laravel cache clean
php artisan config:clear
php artisan route:clear
php artisan view:clear
php artisan cache:clear
6️⃣ Restart Services
systemctl restart php-fpm
systemctl restart nginx # or httpd for Apache
supervisorctl reread
supervisorctl update
supervisorctl restart laravel-worker:*
SUCCESS: After the reload, top showed each worker using ~30 MB instead of 200 MB, and the queue drained in seconds.
VPS or Shared Hosting Optimization Tips
- Prefer a pure Nginx VPS for Laravel; Apache adds unnecessary overhead.
- Allocate at least 2 GB swap if you cannot increase RAM.
- Use
systemdtimers to rotatelogs/worker.logevery 24 h. - On cPanel shared accounts, set
max_execution_timeto 300 inphp.inito avoid silent kills.
Real World Production Example
My SaaS runs on a 2‑CPU, 4 GB Ubuntu 22.04 VPS with Nginx, PHP‑FPM 8.2, and Redis 6.0. Before the fix:
- Average queue throughput: 2 jobs/min
- CPU load: 2.8/4 (heavy spikes)
- Redis latency: 150 ms (timeout every 20 s)
After applying the steps above:
- Throughput: 60 jobs/min (30× improvement)
- CPU load: 0.8/4 (idle headroom)
- Redis latency: 2 ms (steady)
Before vs After Results
| Metric | Before | After |
|---|---|---|
| Jobs/min | 2 | 60 |
| CPU avg % | 87% | 23% |
| Redis latency | 150 ms | 2 ms |
| Memory usage | 3.2 GB | 1.1 GB |
Security Considerations
- Never run
queue:workas root; use a dedicated system user. - Lock down Redis with
requirepassand bind to127.0.0.1 only. - Enable
php-fpmlisten.ownerandlisten.groupto your web user. - Set
supervisorctl statusalerts viamailor Slack for unexpected exits.
WARNING: Disabling stopwaitsecs completely can cause zombie processes during deployments. Keep it low but not zero.
Bonus Performance Tips
- Enable OpCache:
opcache.enable=1and setopcache.memory_consumption=256. - Use
Laravel Horizonon Redis for real‑time monitoring. - Configure Nginx
fastcgi_cachefor API responses that don’t change per request. - Compress Composer autoload with
composer install --optimize-autoloader --no-dev. - Set
APP_ENV=productionandAPP_DEBUG=falseon live VPS.
FAQ
- Q: My queue still hangs after changing Supervisor?
- A: Check the Laravel log (
storage/logs/laravel.log) for fatal Redis errors. Often a firewall blocks port 6379. - Q: Can I use this fix on a shared cPanel host?
- A: You can adjust
php.inivalues and usecroninstead of Supervisor, but you’ll be limited by the provider’s memory caps. - Q: Do I need Nginx, or will Apache work?
- A: Apache works if you enable
mod_proxy_fcgiand tuneMPM_event, but Nginx gives lower latency for API traffic.
Final Thoughts
Queue workers stuck forever are rarely a Laravel bug—they’re a server‑side misconfiguration. By aligning Supervisor, PHP‑FPM, and Redis, you gain predictable throughput and protect your SaaS revenue stream. Spend a few minutes revisiting those configs now and you’ll save hours of debugging later.
Monetize Your Faster Site
Speed translates directly into conversions. If you’re looking for cheap, secure VPS or shared hosting that’s already optimized for PHP, consider Hostinger. Their Laravel‑ready stacks include built‑in Redis, PHP‑FPM tuning, and one‑click SSL—perfect for developers who want to skip the grunt work.
No comments:
Post a Comment