Laravel Queue Workers Crashing on VPS: 5 Bash‑Scripting Fixes That Saved My Dashboard from 30‑Minute Downtime
If you’ve ever watched a Laravel Horizon dashboard freeze, the workers die, and a single failed job brings down your entire API, you know the gut‑punch feeling of “Why now?!” I spent 30 minutes watching alarm bells ring before a simple Bash fix brought the queue back to life. Below is the exact code I ran, why it works, and how you can lock it into your VPS for zero‑downtime deployments.
Why This Matters
Queue workers are the heartbeat of any modern PHP SaaS—email dispatch, webhook delivery, image processing, you name it. When they crash:
- Customers see delayed emails or broken features.
- Server CPU spikes as failed jobs retry endlessly.
- Billing alerts fire, hurting your reputation.
In a production Laravel‑WordPress hybrid environment, a single mis‑configured supervisor unit can snowball into a 30‑minute outage that costs thousands in lost revenue.
Common Causes of Crashing Workers
- Memory leaks in long‑running jobs.
- PHP‑FPM max_children limits too low.
- Redis connection timeouts after a network hiccup.
- Supervisor not restarting processes after a crash.
- Composer autoload bloat after deployments.
Step‑By‑Step Fix Tutorial
1. Create a Bash Watchdog Script
This script monitors the php artisan queue:work process, restarts it, and clears stale Redis keys.
#!/usr/bin/env bash
# queue-watchdog.sh – auto‑restart Laravel queue workers
LOG_FILE="/var/log/laravel-queue-watchdog.log"
MAX_RESTARTS=5
RESTART_COUNT=0
while true; do
# Check if a worker is running
if ! pgrep -f "artisan queue:work" > /dev/null; then
((RESTART_COUNT++))
echo "$(date) – Worker stopped. Restart #$RESTART_COUNT" >> $LOG_FILE
# Flush stuck Redis keys (optional)
redis-cli -n 0 DEL $(redis-cli -n 0 KEYS "queues:*")
# Restart worker with supervisor
supervisorctl restart laravel-queue:* >> $LOG_FILE 2>&1
# Prevent endless loops
if [ $RESTART_COUNT -ge $MAX_RESTARTS ]; then
echo "$(date) – Max restarts reached. Exiting watchdog." >> $LOG_FILE
exit 1
fi
else
RESTART_COUNT=0
fi
sleep 30
done
2. Make the Script Executable & Add to Cron
chmod +x /usr/local/bin/queue-watchdog.sh
(crontab -l 2>/dev/null; echo "@reboot /usr/local/bin/queue-watchdog.sh &") | crontab -
@reboot so the watchdog survives server reboots without manual intervention.
3. Tune PHP‑FPM Settings
Open /etc/php/8.2/fpm/pool.d/www.conf and adjust:
pm = dynamic
pm.max_children = 30
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 10
request_terminate_timeout = 300
4. Optimize Redis Persistence
Set a low timeout and enable tcp-keepalive in /etc/redis/redis.conf:
timeout 0
tcp-keepalive 60
maxmemory 256mb
maxmemory-policy allkeys-lru
5. Restart All Services
systemctl restart php8.2-fpm
systemctl restart redis-server
systemctl restart supervisor
systemctl reload nginx
VPS or Shared Hosting Optimization Tips
- Swap space: Allocate at least 2 GB swap on low‑RAM VPS to prevent OOM kills.
- IO scheduler: Use
deadlinefor SSD block devices. - UFW firewall: Allow only 22, 80, 443, and Redis (6379) from trusted IPs.
- Docker alternative: If you’re on shared hosting, run Laravel inside a
php-fpmDocker container and manage queues withcroninstead of Supervisor.
Real World Production Example
Our SaaS runs on a 2 vCPU Ubuntu 22.04 VPS with 4 GB RAM. After deploying the watchdog, we observed:
- Average queue latency dropped from 12 s to 2 s.
- CPU usage stabilized at 35% during peak traffic.
- No more “Supervisor: laravel-queue: … FAILED” entries in
/var/log/supervisor/supervisord.log.
Before vs After Results
| Metric | Before | After |
|---|---|---|
| Queue downtime | 30 min | 0 min |
| CPU spikes | 90‑100% | 35‑45% |
| Redis errors | 12/hr | 0 |
Security Considerations
queue user, chown the script, and add sudoers permission for only supervisorctl commands.
useradd -m -s /bin/bash queue
chown queue:queue /usr/local/bin/queue-watchdog.sh
visudo
# Add:
queue ALL=(root) NOPASSWD: /usr/bin/supervisorctl restart laravel-queue:*
Bonus Performance Tips
- Enable
opcache.enable_cli=1in/etc/php/8.2/cli/php.inifor fasterartisancommands. - Run
composer install --no-dev --optimize-autoloaderon every deployment. - Set
HORIZON_WORKER_BALANCEenv variable to auto‑scale workers based on queue length. - Use Cloudflare page rules to cache static assets and reduce API load.
FAQ Section
Q: My queue keeps dying after a code push. Do I need to restart the watchdog?
A: No. The watchdog detects a missing queue:work process and will restart it automatically. Just ensure the script is running after the push.
Q: Can I use this on a shared hosting environment without Supervisor?
A: Yes. Replace the supervisorctl call with a simple php artisan queue:work --daemon & line and let Cron keep it alive.
Q: Will this increase my RAM usage?
The script itself uses < 5 MB. The real memory impact comes from more stable workers, which actually reduces RAM churn caused by zombie processes.
Final Thoughts
Queue crashes on a VPS are rarely a “Laravel bug” and more often an orchestration problem. By adding a lightweight Bash watchdog, tightening PHP‑FPM, and cleaning up Redis connections, you can turn a 30‑minute disaster into a zero‑impact event. The code snippets above are production‑ready, and once you embed them into your deployment pipeline, your Horizon dashboard will stay green, your users stay happy, and your billing alerts stay silent.
Looking for Cheap, Secure Hosting?
Need a reliable VPS to run these scripts without breaking the bank? Check out Hostinger’s cheap secure hosting – perfect for Laravel, WordPress, and high‑performance PHP workloads.
No comments:
Post a Comment