Why My Laravel Queue Workers Keep Crashing on Nginx with PHP‑FPM: A Step‑by‑Step Debugging Guide for VPS Deployment
You’ve spent hours watching your php artisan queue:work processes die silently, the logs spike with “connection reset” errors, and the whole API slows to a crawl. It feels like the server is sabotaging your code, right? You’re not alone—developers across the United States hit this wall daily, especially after moving a Laravel app from local dev to a production VPS with Nginx and PHP‑FPM. This guide cuts through the noise, shows you the exact fixes, and gets your workers humming again.
Why This Matters
Queue workers are the heartbeat of any modern Laravel SaaS: they send emails, process images, push notifications, and keep API response times low. When they crash:
- Customer emails never ship → trust drops.
- Background jobs pile up → MySQL spikes, Redis fills, and your VPS runs out of memory.
- CPU usage skyrockets → you pay more for your VPS or get throttled on shared plans.
Fixing the crash isn’t just a convenience; it’s a revenue‑protecting necessity.
Common Causes
Before diving into code, understand the usual suspects on an Ubuntu or Debian VPS:
- PHP‑FPM pool limits (memory, max children).
- Supervisor mis‑configuration (restart policy, user permissions).
- Nginx fastcgi timeouts.
- Redis connection limits or stale sockets.
- Composer autoload cache corruption after a deploy.
- Missing required system extensions (igbinary, redis).
- OOM killer invoked by MySQL or heavy queue payloads.
storage/logs/laravel.log with “Worker stopped with status 255” or “Connection timed out”. Start by grepping for “queue” and “FPM”.Step‑By‑Step Fix Tutorial
1. Verify PHP‑FPM Pool Settings
Open the pool file that matches your site (usually /etc/php/8.2/fpm/pool.d/www.conf).
[www]
user = www-data
group = www-data
listen = /run/php/php8.2-fpm.sock
pm = dynamic
pm.max_children = 25 ; increase from default 5
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 10
pm.max_requests = 5000 ; recycle workers
php_admin_value[memory_limit] = 256M
After editing, restart PHP‑FPM:
sudo systemctl restart php8.2-fpm
pm.max_requests to avoid memory leaks from long‑running jobs.2. Tune Nginx FastCGI Parameters
Edit your site config in /etc/nginx/sites-available/yourdomain.conf:
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_read_timeout 300;
fastcgi_send_timeout 300;
fastcgi_buffer_size 64k;
fastcgi_buffers 8 64k;
fastcgi_busy_buffers_size 128k;
}
Reload Nginx:
sudo nginx -t && sudo systemctl reload nginx
3. Configure Supervisor Properly
Supervisor keeps the workers alive. Create /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 --timeout=120
autostart=true
autorestart=true
user=www-data
numprocs=4
redirect_stderr=true
stdout_logfile=/var/www/html/storage/logs/worker.log
stopwaitsecs=360
environment=QUEUE_CONNECTION="redis",APP_ENV="production"
Update Supervisor and start the processes:
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl status laravel-queue*
4. Optimize Redis Connection
Make sure your .env matches the Redis host and port, and enable persistent connections:
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
REDIS_CLIENT=phpredis
CACHE_DRIVER=redis
QUEUE_CONNECTION=redis
If you’re using a remote Redis server, add tcp_keepalive in /etc/redis/redis.conf and increase maxclients:
maxclients 10000
tcp-keepalive 60
Restart Redis:
sudo systemctl restart redis
5. Clear & Rebuild Composer Autoload
After each deploy, run:
composer install --optimize-autoloader --no-dev
php artisan config:cache
php artisan route:cache
php artisan view:cache
This removes stale class maps that often make workers exit with code 255.
VPS or Shared Hosting Optimization Tips
- Swap Space: Add a 2 GB swap file on low‑memory VPS to give the OOM killer breathing room.
- Ulimit Adjustments:
ulimit -n 65535for high open‑file limits (especially with Redis). - CPU Affinity: Pin PHP‑FPM workers to specific cores via
systemdif you have dedicated cores. - Cloudflare Caching: Offload static asset delivery; reduce request load on Nginx.
- MySQL Tuning: Set
innodb_buffer_pool_sizeto 70% of RAM, enableslow_query_logfor queue‑related queries.
Real World Production Example
Acme SaaS migrated from a 2 GB shared host to a 4 vCPU 8 GB Ubuntu 22.04 VPS. Initial queue crashes were traced to pm.max_children=5 and a missing phpredis extension. After implementing the steps above, the “Failed Jobs” count dropped from 1,342 to 0 in the first 24 hours.
Before vs After Results
| Metric | Before | After |
|---|---|---|
| Avg. Queue Latency | 12 s | 1.3 s |
| CPU Usage (peak) | 95 % | 62 % |
| Memory Footprint | 1.9 GB (OOM) | 1.2 GB |
| Failed Jobs | 342 | 0 |
Security Considerations
- Run workers as
www-dataor a dedicated low‑privilege user. - Never expose
.envvia Nginx:location ~ /\.(?!well-known) { deny all; } - Enable
opcache.validate_timestamps=0in production and deploy withphp artisan opcache:clearto avoid stale code execution. - Limit Redis to localhost or a trusted VPC subnet; enforce
requirepassfor remote instances.
Bonus Performance Tips
- Use
queue:work --daemononly when you have proven memory stability; otherwise preferqueue:listenfor automatic restarts. - Batch heavy jobs (e.g., image processing) with
dispatchNow()orchunk()to reduce DB load. - Leverage Laravel Horizon for real‑time queue monitoring and auto‑scaling on Kubernetes.
- Store large payloads in S3 and reference them via URL in the job payload to keep Redis light.
- Enable
php-fpmrequest_terminate_timeoutto kill runaway scripts after 300 s.
FAQ
Q: My workers still exit with code 255 after all tweaks. What next?
A: Enable error_log = /var/log/php-fpm.log and check journalctl -u php8.2-fpm. Look for segmentation faults – often a missing system library (e.g., libzip).
Q: Can I run Laravel queues on a shared hosting plan?
A: It’s possible with cron* every minute, but you lose concurrency and reliability. For production SaaS, VPS or managed Laravel hosting is recommended.
Q: Should I use Apache instead of Nginx?
A: Nginx’s async model combined with PHP‑FPM delivers lower latency for high‑throughput queue traffic. Apache can work with mod_php, but you’ll lose the fine‑grained process control.
Final Thoughts
Queue worker crashes are rarely a “Laravel bug” – they’re almost always a server‑side resource limitation or mis‑configuration. By systematically tuning PHP‑FPM, Nginx, Supervisor, Redis, and Composer, you turn a flaky VPS into a rock‑solid production engine. Apply the checklist, monitor the logs, and you’ll see latency crush, CPU stabilize, and your SaaS revenue climb.
No comments:
Post a Comment