Laravel Queue Workers Stuck on VPS: Fix PHP‑FPM File Permission Crashes and Redis Temp‑Race in One Hour
You’re staring at a Laravel queue that won’t move, the php-fpm logs are spewing “permission denied,” and Redis keeps throwing READONLY errors. It feels like the whole stack is conspiring against you just before a product launch. You’ve tried restarting services, clearing caches, even rebooting the VPS—nothing. If this sounds familiar, you’re not alone. In the next 45 minutes we’ll untangle file‑system permissions, lock‑step Redis temp‑files, and Supervisor configs so your workers run smooth again.
Why This Matters
Stuck queue workers are more than an annoyance – they cause delayed email delivery, time‑out API calls, and revenue‑leakage in SaaS platforms. On a VPS, a single permission mis‑step can crash php-fpm and cascade into Redis race conditions, turning a healthy Laravel app into a performance nightmare. Fixing it quickly saves you:
- Customer trust – no missed notifications.
- Server resources – avoid runaway
php-fpmprocesses. - Team sanity – stop the “why is it not working?” debugging loop.
Common Causes
- Incorrect file permissions on
/var/www/html/storageor the.sockused by PHP‑FPM. - Redis temp‑file race when multiple workers write to
/tmp/redis-*.rdbsimultaneously. - Supervisor misconfiguration – wrong user, missing
stopwaitsecs, or noautorestartflag. - Out‑of‑sync Composer autoload after a deployment.
- Ubuntu default AppArmor profile restricting PHP‑FPM access.
Step‑By‑Step Fix Tutorial
1. Verify PHP‑FPM User and Socket Permissions
www-data for both Nginx and PHP‑FPM. If you changed the user, adjust all paths accordingly.
# Check php-fpm pool config
cat /etc/php/8.2/fpm/pool.d/www.conf | grep -E 'user|group|listen.owner|listen.group|listen.mode'
# Example output you want:
user = www-data
group = www-data
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
# Ensure socket directory exists and is owned correctly
sudo mkdir -p /run/php
sudo chown www-data:www-data /run/php
sudo chmod 0755 /run/php
# Restart php-fpm
sudo systemctl restart php8.2-fpm
2. Fix Storage & Log Directory Permissions
# Laravel storage permissions (run from project root)
sudo chown -R www-data:www-data storage bootstrap/cache
sudo find storage -type d -exec chmod 2755 {} \;
sudo find storage -type f -exec chmod 0644 {} \;
2 setgid bit preserves group ownership on new files, preventing future permission drift.
3. Resolve Redis Temp‑File Race
# Create a dedicated Redis temp directory with strict permissions
sudo mkdir -p /var/lib/redis/tmp
sudo chown redis:redis /var/lib/redis/tmp
sudo chmod 750 /var/lib/redis/tmp
# Edit redis.conf
sudo sed -i 's|^dir .*|dir /var/lib/redis|' /etc/redis/redis.conf
sudo sed -i 's|^dbfilename .*|dbfilename dump.rdb|' /etc/redis/redis.conf
sudo echo 'temp-dir /var/lib/redis/tmp' >> /etc/redis/redis.conf
# Restart Redis
sudo systemctl restart redis
temp-dir to /tmp on a busy VPS; it leads to the exact race condition you’re fixing.
4. Re‑configure Supervisor for Laravel Queues
# /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
autostart=true
autorestart=true
user=www-data
numprocs=4
redirect_stderr=true
stdout_logfile=/var/www/html/storage/logs/worker.log
stopwaitsecs=360
# Apply changes
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl status laravel-queue:*
5. Composer Autoload Refresh & Cache Clear
# From project root
composer install --optimize-autoloader --no-dev
php artisan config:cache
php artisan route:cache
php artisan view:clear
php artisan cache:clear
6. Nginx FastCGI Pass Adjustments
# /etc/nginx/sites-available/laravel.conf
server {
listen 80;
server_name example.com;
root /var/www/html/public;
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;
fastcgi_intercept_errors on;
fastcgi_read_timeout 300;
}
# Security headers …
}
# Test and reload
sudo nginx -t && sudo systemctl reload nginx
VPS or Shared Hosting Optimization Tips
- Enable
opcache.memory_consumption=256andopcache.validate_timestamps=0on production. - Set
pm.max_childreninwww.confbased onRAM/ (PHP_FPM_MEMORY + 64MB). - Use
systemdwatchdog forphp-fpmif Supervisor isn’t available on shared hosts. - Turn off
swapfor low‑latency queue workloads; rely on proper RAM sizing.
Real World Production Example
Acme SaaS runs 12 Laravel workers on a 4‑CPU, 8 GB Ubuntu 22.04 VPS. After the fix:
- Job latency dropped from 45 s to < 2 s.
- php-fpm memory usage stabilized at 1.1 GB (instead of spiking to 3 GB).
- Redis CPU dropped 30 % because temp‑file locking was removed.
Before vs After Results
# BEFORE
[2024-04-28 14:12:05] production.ERROR: Permission denied while opening /run/php/php8.2-fpm.sock
RedisConnectionException: READONLY You can't write against a read only replica.
# AFTER
[2024-04-28 14:13:12] production.INFO: Queue worker started – pid 3421
[2024-04-28 14:13:12] production.INFO: Redis connection established on 127.0.0.1:6379
Security Considerations
- Never set
listen.mode=0777on the php‑fpm socket; it opens a privilege escalation path. - Keep the Redis temp directory out of the webroot and set
chmod 750. - Enable
ufwrules to allow only your app server IPs to the Redis port. - Audit Composer dependencies with
composer auditafter each deploy.
Bonus Performance Tips
- Use Laravel Horizon for visual queue monitoring; it automatically balances workers.
- Set
REDIS_CLIENT=predisonly if you need PHP‑level fallback; otherwise,phpredisis faster. - Consider
php artisan schedule:workfor cron‑driven jobs to avoidcron*spawns. - Enable
gzipandbrotliin Nginx for API JSON payloads.
FAQ Section
Q: My queue keeps restarting even after fixing permissions. What’s wrong?
A: Check the Supervisor stopwaitsecs value. If a job exceeds that timeout, Supervisor kills the process and restarts it. Increase it to 300 seconds for long‑running jobs.
Q: Can I run Laravel queues on a shared hosting plan?
Yes, but you’ll need to rely on cron * * * * * php /path/artisan schedule:run > /dev/null 2>&1 instead of Supervisor, and you must keep php-fpm socket permissions at the host‑provided defaults.
Q: Why does Redis sometimes show “READONLY” after a failover?
Because the replica became the master after a failover. Update your Laravel .env to point to the new master IP or use Redis Sentinel for automatic failover handling.
Q: Do I need to restart Nginx after fixing the socket?
Only if you changed the fastcgi_pass path. Otherwise a simple systemctl reload php8.2-fpm is sufficient.
Final Thoughts
Queue workers on a VPS don’t have to be a black box. By tightening file permissions, isolating Redis temp files, and giving Supervisor a clean slate, you can recover from a stuck state in under an hour and keep your Laravel app scaling smoothly. The same principles apply to WordPress performance—consistent permissions, proper PHP‑FPM tuning, and Redis isolation are universal wins.
Ready to ship faster? A well‑tuned VPS saves you hours of debugging and lets you focus on building features, not firefighting.
No comments:
Post a Comment