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:
- Incorrect ownership – Files belong to
rootinstead of the web‑user (oftenwww-dataorapache). - Missing execute bit – The
artisanbinary or custom scripts aren’t executable. - Composer vendor cache corruption – A partially‑installed package can trigger
Class not founderrors. - Supervisor config points to the wrong PHP binary – Using the system PHP (7.2) while the app needs 8.1.
- Read‑only temporary directories –
storage/framework/cacheorbootstrap/cachecan’t write.
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
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
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_childrenbased on available RAM (e.g.,pm.max_children = 8for 2 GB VPS). - Enable OPcache in
php.inifor 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).
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 640for.envand keep it outside the web root when possible. - Restrict SSH keys to a single user and disable password authentication.
- Enable
fail2banor 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
gzipin 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.logfor uncaught exceptions. Most often it’s a missing environment variable or a stale Redis key. Runphp artisan queue:restartafter 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 onphp artisan schedule:runevery 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