Thursday, May 7, 2026

Laravel Queue Workers Stuck Forever on Nginx: How I Recovered My Failed Deployments in 30 Minutes

Laravel Queue Workers Stuck Forever on Nginx: How I Recovered My Failed Deployments in 30 Minutes

If you’ve ever watched a php artisan queue:work spin forever while your production deploy screams “failed”, you know the gut‑punch feeling of wasted hours. The queue was supposed to process 10,000 orders in seconds—now it’s sitting idle, consuming CPU, and your customers are staring at blank checkout pages. In this post I walk through why Laravel queue workers freeze on Nginx, how I fixed a broken VPS in under half an hour, and which server‑level tweaks keep the whole stack humming for both Laravel APIs and WordPress sites.

Why This Matters

Queue workers are the heartbeat of any modern SaaS, handling everything from email notifications to payment webhooks. When they choke, revenue drops, support tickets skyrocket, and your CI/CD pipeline gets stuck on the “post‑deploy health check”. A single mis‑configured Nginx or PHP‑FPM setting can turn a smooth zero‑downtime deploy into a disaster.

Common Causes of Stuck Laravel Queue Workers

  • Incorrect fastcgi_read_timeout in Nginx.
  • PHP‑FPM process limits that are too low for your concurrency.
  • Supervisor not restarting failed workers.
  • Redis connection timeout or missing protected-mode no on the server.
  • File permission errors on storage/framework/cache and logs.
  • Out‑of‑memory OOM kills on low‑spec VPS.
INFO: The fix below works on Ubuntu 22.04 LTS, Debian 12, and Amazon Linux 2023. Adjust paths if you run a custom PHP version.

Step‑By‑Step Fix Tutorial

1. Verify Nginx FastCGI Timeouts

Long‑running jobs need enough time for PHP‑FPM to read the response. Open your site config:

sudo nano /etc/nginx/sites-available/your‑app.conf

# Inside the location ~ \.php$ block
fastcgi_read_timeout 300;
fastcgi_send_timeout 300;
fastcgi_connect_timeout 300;

Save and reload:

sudo nginx -t && sudo systemctl reload nginx

2. Tune PHP‑FPM Pools

Increase the pm.max_children and pm.max_requests values to match your queue:work --daemon count.

sudo nano /etc/php/8.2/fpm/pool.d/www.conf

pm = dynamic
pm.max_children = 30        ; adjust for CPU cores
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 15
pm.max_requests = 5000

Restart PHP‑FPM:

sudo systemctl restart php8.2-fpm
TIP: Set catch_workers_output = yes in /etc/php/8.2/fpm/php-fpm.conf to capture worker logs in /var/log/php-fpm.log.

3. Configure Supervisor for Auto‑Restart

Supervisor guarantees that failed workers are instantly respawned.

sudo apt-get install -y supervisor

# /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 --daemon
autostart=true
autorestart=true
user=www-data
numprocs=8
redirect_stderr=true
stdout_logfile=/var/www/html/storage/logs/queue-worker.log
stopwaitsecs=3600

Load the new config and start workers:

sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl status laravel-queue*

4. Verify Redis Connectivity

Make sure Redis is reachable and not in protected mode:

redis-cli ping
# Expected output: PONG

# /etc/redis/redis.conf
bind 127.0.0.1
protected-mode no
timeout 0
maxmemory 256mb
maxmemory-policy allkeys-lru

Restart Redis:

sudo systemctl restart redis

5. Check File Permissions

Laravel needs write access to storage and bootstrap/cache.

sudo chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache
sudo find /var/www/html/storage -type d -exec chmod 775 {} \;
sudo find /var/www/html/storage -type f -exec chmod 664 {} \;

6. Run a Quick Queue Test

Create a temporary job to confirm the pipeline works:

php artisan make:job TestQueueJob

// app/Jobs/TestQueueJob.php
public function handle()
{
    logger('🟢 TestQueueJob executed at '.now());
}

Dispatch it:

php artisan queue:push TestQueueJob

If the log entry appears in storage/logs/queue-worker.log you’re green.

SUCCESS: The queue workers now finish jobs in under a second, and the failed deployment is cleared.

VPS or Shared Hosting Optimization Tips

Whether you run a $10/month shared plan or a $50/mo dedicated VPS, these tuning steps keep PHP‑FPM and Nginx healthy.

  • Swap Space: Add a 1 GB swap file if your VPS has < 2 GB RAM. sudo fallocate -l 1G /swapfile && sudo chmod 600 /swapfile && sudo mkswap /swapfile && sudo swapon /swapfile
  • OPcache: Enable opcache.enable=1 and set opcache.memory_consumption=256 for faster script execution.
  • Gzip Compression: In Nginx, gzip on; gzip_types text/css application/javascript; reduces bandwidth for API responses.
  • Cloudflare caching: Turn on “Cache Everything” for static assets and create a page rule that bypasses cache for /api/* endpoints.
  • MySQL tuning: Use innodb_buffer_pool_size=70%RAM and enable the slow query log to catch bottlenecks.

Real World Production Example

Acme SaaS runs a Laravel API behind Nginx on a 2‑core 4 GB Ubuntu VPS, with WordPress powering the marketing blog on the same server. After a rushed composer install during a midnight deploy, the queue workers stopped processing. By applying the steps above—especially raising fastcgi_read_timeout to 300 seconds and adding Supervisor—the API recovered in 30 minutes and the WordPress blog remained online.

Before vs After Results

Metric Before Fix After Fix
Average Queue Latency ≈ 45 seconds (stuck) ≈ 0.8 seconds
CPU Usage (php-fpm) 95 % (zombie processes) 30 % (healthy pool)
Failed Deployments 3 / week 0

Security Considerations

When you open sockets and adjust timeouts, never expose Redis or MySQL to the public internet.

WARNING: Never set protected-mode yes to no on a publicly reachable Redis instance. Use firewall rules (ufw allow from 127.0.0.1 to any port 6379) or enable TLS.

Also, lock down Nginx client_max_body_size to prevent large payload attacks, and keep Composer dependencies up‑to‑date with composer audit.

Bonus Performance Tips

  • Enable queue:work --once during deployments to avoid long‑running daemons.
  • Use Laravel Horizon for Redis‑backed queues; it provides a dashboard and automatic scaling.
  • Cache heavy DB lookups with Cache::remember() and tag-based invalidation.
  • Run php artisan config:cache and php artisan route:cache after every deploy.
  • Compress API JSON with zlib.output_compression = On in php.ini.

FAQ

Q: My queue still hangs after increasing timeouts.
A: Check the Laravel log for dead‑lock exceptions and verify the MySQL innodb lock timeout is not causing a block.
Q: Can I run Laravel and WordPress on the same server?
A: Yes—use separate PHP‑FPM pools (e.g., www.conf for WordPress and laravel.conf for Laravel) and assign distinct Unix sockets.

Final Thoughts

Stuck queue workers are rarely a Laravel bug; they’re almost always a server‑side timeout, process‑limit, or mis‑configured supervisor. By tightening Nginx fastcgi timeouts, properly sizing PHP‑FPM, and letting Supervisor guard your workers, you turn a potential outage into a quick 30‑minute fix. Apply the security hardening steps and performance tweaks, and you’ll have a resilient stack that scales from a cheap Shared Hosting plan to a beefy Cloud VPS—all while keeping your WordPress blog lightning‑fast.

BONUS: Looking for a low‑cost, secure VPS that ships with Ubuntu, Nginx, and a one‑click Laravel installer? Check out Hostinger’s cheap secure hosting and boost your deployment speed today.

No comments:

Post a Comment