How to Stop Laravel Queue Workers Crashing on a VPS: 5 Dead‑On PHP‑FPM & Redis Fixes That Restore 99.99% Uptime in Minutes
If you’ve ever watched a Laravel queue die mid‑job, felt that gut‑punch of panic, and then chased log files for hours, you’re not alone. In production, a single worker crashing can cascade into delayed emails, broken API responses, and angry users. The good news? Most crashes are not “mystical” Laravel bugs—they’re mis‑tuned PHP‑FPM, Redis time‑outs, or a tiny Supervisor mis‑config that you can fix in under ten minutes.
Why This Matters
A reliable queue is the backbone of modern SaaS, especially when you’re serving thousands of API calls, sending newsletters, or processing image thumbnails on a shared VPS. Even a 0.01% crash rate translates to lost revenue, higher churn, and a support ticket backlog. Optimizing the stack (PHP‑FPM, Redis, Nginx/Apache, MySQL) not only prevents crashes but also improves overall response time—something every WordPress‑Laravel hybrid site craves.
Common Causes of Queue Worker Crashes
- PHP‑FPM
pm.max_childrentoo low → workers get killed by the OS. - Redis
timeoutortcp-keepalivemis‑configuration causing lost connections. - Supervisor
autorestartset tounexpectedwith a bad exit code. - Memory leaks in third‑party packages or Composer autoload optimization missing.
- OOM killer activation on low‑memory VPS instances (often 1‑2 GB).
storage/logs/laravel.log. Start by tail‑ing the log while you fire a job.Step‑By‑Step Fix Tutorial
1. Tune PHP‑FPM for Heavy Queue Loads
Open the pool config (usually /etc/php/8.1/fpm/pool.d/www.conf) and adjust:
# Increase children to match CPU cores and RAM
pm = dynamic
pm.max_children = 30
pm.start_servers = 6
pm.min_spare_servers = 4
pm.max_spare_servers = 12
# Reduce request timeout for queue workers
request_terminate_timeout = 300
After editing, restart PHP‑FPM:
sudo systemctl restart php8.1-fpm
2. Harden Redis Connectivity
Modify /etc/redis/redis.conf:
# Set a sane client timeout (seconds)
timeout 10
# Enable TCP keepalive to avoid silent drops
tcp-keepalive 60
# Bind to localhost only if not using a dedicated cache server
bind 127.0.0.1 ::1
protected-mode yes
Then restart Redis:
sudo systemctl restart redis-server
--tcp-keepalive 60 to the run command.3. Optimize Supervisor Config
Typical /etc/supervisor/conf.d/laravel-queue.conf for 4 workers:
[program:laravel-queue]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/html/artisan queue:work redis --sleep=3 --tries=3 --timeout=300
autostart=true
autorestart=true
startsecs=5
stopwaitsecs=360
user=www-data
numprocs=4
redirect_stderr=true
stdout_logfile=/var/log/laravel/queue.log
Reload Supervisor:
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl status laravel-queue
4. Composer Autoload & Opcache
Run these commands on every deploy:
composer install --optimize-autoloader --no-dev
php artisan config:cache
php artisan route:cache
php artisan view:cache
Enable Opcache in /etc/php/8.1/fpm/php.ini:
opcache.enable=1
opcache.memory_consumption=192
opcache.max_accelerated_files=10000
opcache.validate_timestamps=0
5. Adjust Linux Swappiness & OOM Settings
On a low‑memory VPS, set swappiness to 10 and protect the PHP‑FPM process group:
# Reduce swap usage
sysctl vm.swappiness=10
echo "vm.swappiness = 10" >> /etc/sysctl.conf
# Add PHP‑FPM to OOM score adjustment
echo -1000 > /proc/$(pgrep php-fpm)/oom_score_adj
VPS or Shared Hosting Optimization Tips
- Use a dedicated swap file (2 GB) on cheap VPS to give Redis a buffer.
- Separate queue processing from web workers on shared hosts; use a cron‑based
queue:work --daemonif Supervisor isn’t available. - Enable Cloudflare caching for static assets to free up PHP‑FPM cycles.
- Upgrade to PHP 8.2 when possible – the JIT engine slashes execution time for CPU‑bound jobs.
Real World Production Example
Company Acme SaaS runs a Laravel API on a 2 CPU, 4 GB Ubuntu 22.04 VPS behind Nginx. Before applying the fixes, average queue latency spiked to 45 seconds during midnight traffic, and the supervisorctl status showed “FATAL” for two workers.
After implementing the five steps:
- Latency dropped to 2.8 seconds average.
- Memory usage stabilized at 1.2 GB.
- No worker crash for 30 days straight.
Before vs After Results
| Metric | Before | After |
|---|---|---|
| Avg Queue Latency | 45 s | 2.8 s |
| Worker Crashes / month | 8 | 0 |
| RAM Usage (peak) | 3.8 GB | 1.6 GB |
Security Considerations
- Never expose Redis to the public internet – keep it bound to
127.0.0.1or use a VPC. - Set
permissionsonstorage/to750and userwww-dataonly. - Enable
APP_ENV=productionandAPP_DEBUG=falsein.env. - Use a firewall (UFW/iptables) to restrict SSH to your IP range.
Bonus Performance Tips
- Batch Redis writes: use
pipeline()when storing job results. - Horizontal scaling: spin up another VPS and share the same Redis instance; configure
queue:work --daemon --timeout=300on each node. - Use Horizon for visual monitoring and auto‑scaling of workers.
- Compress large payloads before pushing to the queue (gzip or base64).
FAQ
Q: My VPS has only 1 GB RAM. Can I still run 4 workers?
A: Reduce pm.max_children to 8 and set queue:work --sleep=5. Consider moving Redis to a managed service (e.g., Upstash) to free RAM.
Q: Should I use Apache instead of Nginx?
Both work, but Nginx’s event‑driven model uses less memory under high concurrency, which aligns better with low‑cost VPS plans.
Q: Why does php artisan queue:restart sometimes not revive workers?
Supervisor may still hold the old PID. Run supervisorctl restart laravel-queue:* to force a clean reload.
Final Thoughts
Queue stability is a matter of three things: proper PHP‑FPM sizing, resilient Redis connections, and a rock‑solid process manager. By applying the five fixes above you can guarantee 99.99% uptime, shave seconds off every job, and free yourself from midnight panic attacks. Your users will notice faster emails, API responses, and smoother WordPress‑Laravel integration—all without spending a fortune on a dedicated server.
No comments:
Post a Comment