Saturday, May 9, 2026

Why My Laravel Queue Workers Keep Crashing on Nginx with PHP‑FPM: A Step‑by‑Step Debugging Guide for VPS Deployment

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:

  1. PHP‑FPM pool limits (memory, max children).
  2. Supervisor mis‑configuration (restart policy, user permissions).
  3. Nginx fastcgi timeouts.
  4. Redis connection limits or stale sockets.
  5. Composer autoload cache corruption after a deploy.
  6. Missing required system extensions (igbinary, redis).
  7. OOM killer invoked by MySQL or heavy queue payloads.
INFO: Most crashes surface in 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
TIP: Set 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*
WARNING: Do NOT run the queue as root. Permissions errors are a common cause of silent crashes.

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 65535 for high open‑file limits (especially with Redis).
  • CPU Affinity: Pin PHP‑FPM workers to specific cores via systemd if you have dedicated cores.
  • Cloudflare Caching: Offload static asset delivery; reduce request load on Nginx.
  • MySQL Tuning: Set innodb_buffer_pool_size to 70% of RAM, enable slow_query_log for queue‑related queries.
SUCCESS: After applying these tweaks, a typical 8‑core 16 GB VPS can sustain 300+ concurrent workers without crashing.

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-data or a dedicated low‑privilege user.
  • Never expose .env via Nginx: location ~ /\.(?!well-known) { deny all; }
  • Enable opcache.validate_timestamps=0 in production and deploy with php artisan opcache:clear to avoid stale code execution.
  • Limit Redis to localhost or a trusted VPC subnet; enforce requirepass for remote instances.

Bonus Performance Tips

  1. Use queue:work --daemon only when you have proven memory stability; otherwise prefer queue:listen for automatic restarts.
  2. Batch heavy jobs (e.g., image processing) with dispatchNow() or chunk() to reduce DB load.
  3. Leverage Laravel Horizon for real‑time queue monitoring and auto‑scaling on Kubernetes.
  4. Store large payloads in S3 and reference them via URL in the job payload to keep Redis light.
  5. Enable php-fpm request_terminate_timeout to 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.

Bonus Offer: Need a fast, secure VPS with 1‑click Laravel deployment? Check out Hostinger’s cheap secure hosting – perfect for scaling Laravel queues without the headache.

No comments:

Post a Comment