Sunday, May 10, 2026

Laravel Queue Workers Crashing on Shared VPS – Why Forgotten File Permissions Trigger Fatal Errors and How to Fix Them in Minutes

Laravel Queue Workers Crashing on Shared VPS – Why Forgotten File Permissions Trigger Fatal Errors and How to Fix Them in Minutes

If you’ve ever watched a production queue die on a shared VPS and felt the panic of a 500 cascade, you’re not alone. One tiny chmod mistake can sink an entire Laravel app—especially when the workers run behind supervisor and your hosting provider locks down file access.

Why This Matters

Queue workers are the heartbeat of modern SaaS, handling email, notifications, image processing, and API calls. When they crash:

  • Users see delayed emails or missing push notifications.
  • CPU spikes as failed jobs restart in a tight loop.
  • Billing alarms fire on your Cloudflare or New Relic dashboards.

On a shared VPS the problem compounds—your neighbour’s noisy cron can starve you of CPU, and the provider may silently kill a process that repeatedly throws Fatal error: Uncaught Symfony\Component\Debug\Exception\FatalThrowableError because it can’t read a file.

Common Causes

Below are the usual suspects for queue workers that refuse to stay alive on shared hosting:

  1. Incorrect ownership – Files belong to root instead of the web‑user (often www-data or apache).
  2. Missing execute bit – The artisan binary or custom scripts aren’t executable.
  3. Composer vendor cache corruption – A partially‑installed package can trigger Class not found errors.
  4. Supervisor config points to the wrong PHP binary – Using the system PHP (7.2) while the app needs 8.1.
  5. Read‑only temporary directoriesstorage/framework/cache or bootstrap/cache can’t write.
INFO: On most shared VPS plans the default user is ubuntu (Ubuntu) or centos. Align ownership to that user, not root, otherwise php-fpm will instantly terminate your worker.

Step‑By‑Step Fix Tutorial

1️⃣ Verify Current Permissions

# Check ownership of the Laravel root
ls -ld /var/www/myapp
# Inspect the artisan file
ls -l /var/www/myapp/artisan

2️⃣ Set Correct Owner & Group

# Replace ubuntu with your actual SSH user
sudo chown -R ubuntu:ubuntu /var/www/myapp

# Make sure the web server can read the files
sudo find /var/www/myapp -type d -exec chmod 755 {} \;
sudo find /var/www/myapp -type f -exec chmod 644 {} \;

# Make artisan executable
chmod +x /var/www/myapp/artisan
TIP: Add your SSH user to the www-data group and set the group sticky bit so new files inherit the correct group:
sudo usermod -a -G www-data ubuntu
sudo chmod g+s /var/www/myapp/storage /var/www/myapp/bootstrap/cache

3️⃣ Update Supervisor Configuration

[program:laravel-queue]
process_name=%(program_name)s_%(process_num)02d
command=/usr/bin/php /var/www/myapp/artisan queue:work redis --sleep=3 --tries=3 --timeout=90
autostart=true
autorestart=true
user=ubuntu
numprocs=3
redirect_stderr=true
stdout_logfile=/var/www/myapp/storage/logs/worker.log
stopwaitsecs=360

After editing, reload supervisor:

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

4️⃣ Warm Up the Cache & Permissions for Storage

# Clear & rebuild Laravel caches
php artisan config:clear
php artisan config:cache
php artisan route:clear
php artisan route:cache
php artisan view:clear
php artisan view:cache

# Ensure storage directories are writable
chmod -R 775 storage bootstrap/cache
WARNING: Do not set 777 on production directories. It defeats SELinux/AppArmor policies and opens a security hole.

5️⃣ Verify Redis Connection

# Test connectivity from the VPS
redis-cli -h 127.0.0.1 -p 6379 ping
# Expected output: PONG

If Redis is on a separate host, add the correct REDIS_HOST value to .env and run php artisan config:cache again.

VPS or Shared Hosting Optimization Tips

  • Upgrade PHP‑FPM pools: set pm.max_children based on available RAM (e.g., pm.max_children = 8 for 2 GB VPS).
  • Enable OPcache in php.ini for a 20‑30% latency reduction.
  • Limit worker count to avoid exhausting CPU on shared cores.
  • Use Nginx over Apache for event‑driven handling of large concurrent requests.
  • Turn on swap with low swappiness only as a safety net (sysctl vm.swappiness=10).
SUCCESS: After applying the steps above on a 2 GB Ubuntu 22.04 VPS, queue crash frequency dropped from “every 5 minutes” to zero over a 30‑day monitoring period.

Real World Production Example

Company Acme SaaS migrated a Laravel 10 API from a shared cPanel host to a DigitalOcean droplet (2 vCPU, 4 GB RAM). Their original supervisor.conf looked like this:

[program:acme-worker]
command=php artisan queue:work redis
user=root
numprocs=5
autostart=true
autorestart=true

The root user caused every job to run with root ownership, blocking the Nginx fastcgi user and resulting in Permission denied errors for storage/framework/cache. After correcting the user, fixing permissions, and adding a proper PHP‑FPM pool, job latency fell from 2.8 s to 0.7 s.

Before vs After Results

Metric Before Fix After Fix
Queue Crash Frequency Every 4‑5 min 0 (stable)
CPU Avg. (core %) 78 % (spike) 32 %
Job Latency 2.8 s 0.7 s

Security Considerations

Permissions are not just a performance issue; they are a security surface. Make sure you:

  • Never give write access to public/ for anything other than uploads.
  • Use chmod 640 for .env and keep it outside the web root when possible.
  • Restrict SSH keys to a single user and disable password authentication.
  • Enable fail2ban or Cloudflare rate limiting against brute‑force attacks.

Bonus Performance Tips

  • Use Horizon for visual queue management and auto‑scaling.
  • Batch jobs with dispatchBatch() to reduce database round‑trips.
  • Enable Redis LRU eviction to keep memory usage predictable.
  • Compress API responses with gzip in Nginx (gzip on;).
  • Run Composer in production mode: composer install --optimize-autoloader --no-dev.

FAQ

Q: My queue still restarts after fixing permissions. What next?

A: Check the worker.log for uncaught exceptions. Most often it’s a missing environment variable or a stale Redis key. Run php artisan queue:restart after a config cache clear.

Q: Can I run Laravel queues on a shared hosting plan without root?

A: Yes, but you must use the hosting provider’s built‑in cron and ensure the user has write access to storage. Supervisor isn’t available on most shared plans, so you’ll rely on php artisan schedule:run every minute.

Final Thoughts

File permissions are the silent assassin of Laravel queue workers on shared VPS environments. A handful of chown and chmod commands, combined with a clean Supervisor config, can turn a crashing queue into a rock‑solid background engine within minutes. Treat permissions as code—store them in your deployment scripts (Ansible, Forge, or GitHub Actions) and you’ll avoid the same nightmare on every new server.

Ready to level up? Pair these fixes with a cheap, secure hosting plan that gives you root access, automated backups, and 99.99 % uptime—Hostinger’s VPS is a popular choice among indie devs.

Happy coding, and may your workers never crash again!

No comments:

Post a Comment