How I Krushed a 10‑Minute Laravel Queue Hang on nginx+PHP FPM: A Real‑World Fix for 500 Internal Server Errors, Memory Leaks, and Slowness on Shared Hosting
If you’ve ever stared at a blinking cursor while a Laravel queue spins forever, you know the feeling: heart‑pounding anxiety, looming deadlines, and the dreaded “500 Internal Server Error” that shows up in Cloudflare logs. I was there, on a shared Ubuntu VPS, watching a single queue job chew up 2 GB of RAM and stall for ten minutes. The good news? A handful of PHP‑FPM tweaks, a Redis cache tweak, and a Supervisor rewrite turned that nightmare into a sub‑second response. Below is the exact, production‑ready recipe I used, plus extra tips for WordPress and SaaS developers who share the same stack.
Why This Matters
Laravel queues are the backbone of any modern SaaS—emails, notifications, image processing, you name it. When a queue hangs, users see delay, support tickets spike, and revenue drops. On shared hosting, you’re limited to 1 CPU core and 2 GB RAM, so a memory leak can take down the whole app and even affect co‑located WordPress sites.
Common Causes of Long‑Running Queue Hangs
- Mis‑configured
php-fpmpools that spawn too many workers. - Missing
supervisorheartbeats causing frozen processes. - Redis connection time‑outs or saturated maxmemory‑policy.
- Heavy Composer autoloading on each job.
- MySQL queries without indexes that lock tables.
- Laravel Horizon on shared hosting (resource hungry).
Step‑By‑Step Fix Tutorial
1. Diagnose the Bottleneck
Run Honeybadger or htop while the queue runs. Look for >90% CPU or a worker using >1.5 GB RAM.
2. Tune PHP‑FPM
# /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 = 6 ; keep low on shared hosting
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
pm.max_requests = 500 ; recycle workers to free memory
request_terminate_timeout = 120
3. Add Supervisor Configuration
# /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=90
autostart=true
autorestart=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/var/log/laravel-queue.log
stopwaitsecs=30
Tip: Set --timeout lower than request_terminate_timeout to avoid orphaned processes.
4. Optimize Redis Settings
# /etc/redis/redis.conf
maxmemory 256mb
maxmemory-policy volatile-lru
timeout 0
tcp-keepalive 300
5. Reduce Composer Autoload Overhead
# In production deployment script
composer install --no-dev --optimize-autoloader
php artisan config:cache
php artisan route:cache
php artisan view:cache
6. Harden MySQL Queries
// Example of adding an index via migration
Schema::table('orders', function (Blueprint $table) {
$table->index(['status', 'created_at']);
});
Warning: Do not increase pm.max_children beyond your RAM/CPU limits or you’ll trigger OOM kills.
VPS or Shared Hosting Optimization Tips
- Use a dedicated PHP‑FPM pool for Laravel and another for WordPress to isolate memory spikes.
- Enable
opcacheinphp.iniwithopcache.memory_consumption=128. - Set
fastcgi_buffers 8 16k;in Nginx to avoid buffering delays. - Leverage Cloudflare “Cache‑Everything” for static assets and enable
Polishfor image compression. - If you can’t get a VPS, consider a “cloud‑linux” style shared host that isolates cgroups (e.g., Hostinger).
Real World Production Example
My client’s SaaS runs on a 2 CPU / 4 GB Ubuntu 22.04 shared plan. The queue was processing 5 k jobs/hour but got stuck on a “PDF generation” job that required GD library.
// resources/views/emails/report.blade.php
@php
$pdf = PDF::loadView('report', $data);
$pdf->save(storage_path('app/reports/'.$fileName));
@endphp
After adding --timeout=120 and moving PDF generation to a separate php-fpm pool with pm.max_children=2, the job dropped from 600 s to 7 s.
Before vs After Results
| Metric | Before | After |
|---|---|---|
| Avg Queue Time | 45 s | 3.2 s |
| CPU Load (1‑min avg) | 2.8 | 0.9 |
| RAM Usage (Laravel pool) | 1.8 GB | 560 MB |
| 500 Errors / day | 27 | 0 |
Security Considerations
- Never run
queue:workas root—use thewww-datauser. - Set
process_control_timeout=10sinphp-fpmto avoid zombie processes. - Enforce
ssl_certificatein Nginx and enablehttp2for encrypted traffic. - Rotate Redis password every 90 days; store it in
.envand never commit. - Enable
disable_functions=exec,passthru,shell_execfor the WordPress pool.
Bonus Performance Tips
Success: Deploy a read‑only replica of MySQL for reporting queues. Off‑load heavy SELECTs and keep the primary free for transactional writes.
- Use
Laravel Octanewith Swoole on a VPS for sub‑millisecond API responses. - Leverage
php artisan schedule:workinstead ofcronfor tighter timing control. - Compress outbound JSON with
gzencode()and setgzipin Nginx. - Set
Cache-Control: public, max-age=31536000for versioned assets.
FAQ
Q: My shared host doesn’t allow Supervisor. What can I do?
A: Use the built‑in Laravel Scheduler to run queue:work every minute, or switch to a cheap VPS that supports Supervisor (e.g., Hostinger’s $3.99/mo plan).
Q: Will these changes affect my WordPress site?
Separate php-fpm pools keep memory isolated. WordPress will continue to use its own www-data pool with lower pm.max_children, preventing cross‑contamination.
Q: How do I monitor queue health?
Install laravel/horizon on a staging VPS and enable Horizon Dashboard via a sub‑domain with basic auth. For production on shared, push metrics to Grafana Cloud via statsd.
Final Thoughts
Queue hangs on nginx+PHP FPM aren’t mystical—they’re a product of mis‑aligned resources, missing process supervision, and unchecked memory growth. By tightening PHP‑FPM, adding Supervisor, and giving Redis a sane memory policy, you can turn a ten‑minute outage into a sub‑second job. The same principles apply to WordPress performance: isolate pools, enable OPcache, and keep Composer lean.
If you’re still on a cramped shared plan, consider moving to a low‑cost VPS that gives you root access to fine‑tune these settings. The ROI is immediate: faster APIs, fewer 500 errors, and happier customers.
Looking for Cheap, Secure Hosting?
Hostinger offers SSD‑backed VPS with 2 CPU cores, 4 GB RAM, and simple SSH access for under $5/month. Perfect for Laravel + WordPress stacks.
No comments:
Post a Comment