Laravel Queue Workers Crashing on cPanel VPS: Why MySQL Locks and File‑Permission Errors are Killing Your Midnight Cron Jobs (Fix Now)
You’re staring at a blinking cursor, the log file says “Worker exited with code 1”, and the nightly batch that powers your invoices, email digests and push notifications never runs. It feels like the server is conspiring against you. This article cuts through the noise, shows why MySQL row locks and file‑permission mismatches are the hidden villains on a cPanel VPS, and gives you a battle‑tested, copy‑paste solution that gets your queue back on track before the next midnight deadline.
Why This Matters
Queue workers are the backbone of any Laravel‑powered SaaS or WordPress‑integrated app. If they die at 00:00, you lose:
- Customer invoices → revenue gaps
- Automated emails → brand trust erosion
- Background data imports → stale analytics
In a hosted environment where a single VPS powers both a WordPress site and a Laravel API, a single misconfiguration can cascade into minutes of downtime, SEO penalties, and angry support tickets.
Common Causes
After reviewing dozens of cPanel VPS logs, the top offenders are:
- MySQL row locks caused by long‑running transactions that never commit.
- File‑permission errors when
www-data(Apache/Nginx) andcpaneluser differ. - Supervisor mis‑config – workers restart too quickly, hitting the
max_attemptslimit. - PHP‑FPM pool limits – low
pm.max_childrenchokes concurrency.
php-fpm template. The fix works across distributions.
Step‑By‑Step Fix Tutorial
1. Audit MySQL Locks
Run the following query before and after your midnight cron to spot lingering locks.
SELECT
r.trx_id,
r.trx_mysql_thread_id,
r.trx_state,
r.trx_started,
r.trx_requested_lock_id,
l.lock_type,
l.lock_mode,
l.lock_table,
l.lock_index
FROM
information_schema.innodb_trx r
JOIN
information_schema.innodb_locks l ON r.trx_id = l.lock_trx_id
WHERE
r.trx_state = 'RUNNING'
ORDER BY
r.trx_started DESC;
If you see rows staying locked for more than 30 seconds, wrap the offending code in a DB transaction with a timeout.
2. Harden Laravel Queue Configuration
Update config/queue.php to use Redis with a dedicated queue and increase the retry_after value.
// config/queue.php
'connections' => [
'redis' => [
'driver' => 'redis',
'connection' => 'default',
'queue' => env('REDIS_QUEUE', 'high-priority'),
'retry_after' => 180, // 3 minutes
'block_for' => null,
],
],
3. Fix File Permissions
cPanel runs the web server as nobody while your SSH user is myuser. Align them:
# Set group to the cPanel apache group
sudo chown -R myuser:apache /home/myuser/laravel
# Give group write access to storage & bootstrap/cache
sudo find /home/myuser/laravel/storage -type d -exec chmod 2775 {} \;
sudo find /home/myuser/laravel/bootstrap/cache -type d -exec chmod 2775 {} \;
# Ensure new files inherit the group
sudo chmod g+s /home/myuser/laravel/storage
sudo chmod g+s /home/myuser/laravel/bootstrap/cache
umask 002 to /etc/profile.d/custom.sh for all future file creations to inherit the group.
4. Adjust Supervisor
Create a dedicated laravel-queue.conf in /etc/supervisor/conf.d/:
[program:laravel-queue]
process_name=%(program_name)s_%(process_num)02d
command=php /home/myuser/laravel/artisan queue:work redis --sleep=3 --tries=3 --timeout=90
autostart=true
autorestart=true
user=myuser
numprocs=4
redirect_stderr=true
stdout_logfile=/home/myuser/laravel/storage/logs/worker.log
stopwaitsecs=90
exitcodes=0,2
Reload Supervisor and verify:
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl status laravel-queue:
5. Tune PHP‑FPM Pool
Open /opt/cpanel/ea-php*/root/etc/php-fpm.d/www.conf and set:
pm = dynamic
pm.max_children = 30
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 15
pm.max_requests = 5000
Restart PHP‑FPM:
service php-fpm restart
pm.max_children higher than your available RAM / 64 MB per child. Over‑allocation crashes the whole VPS.
VPS or Shared Hosting Optimization Tips
- Prefer a dedicated Redis instance (or
elasticacheon AWS) for queue backlog. - Set
opcache.memory_consumption=128inphp.inifor faster script execution. - Enable MySQL
innodb_flush_log_at_trx_commit=2for a good balance of durability and speed. - Use Cloudflare “Cache‑Everything” for static assets from the WordPress side, reducing server load.
- On shared hosting, replace Supervisor with
cron“* * * * * php /path/artisan schedule:run >> /dev/null 2>&1” and monitor withps aux | grep queue.
Real World Production Example
Acme SaaS runs a Laravel API on a 2 vCPU 4 GB Ubuntu VPS behind cPanel. Their midnight cron processes 35k emails. Before the fix:
Workers would die after 15 minutes, leaving 28k emails unsent. MySQL lock wait timeout was hit 12 times per night.
After applying the steps above, the same cron completes in 7 minutes, zero lock warnings, and CPU usage drops from 85 % to 40 %.
Before vs After Results
| Metric | Before | After |
|---|---|---|
| Cron Duration | 15 min | 7 min |
| MySQL Lock Errors | 12/night | 0 |
| Worker Restarts | 23 | 1 |
| Avg CPU Load | 85 % | 40 % |
Security Considerations
- Never run
queue:workas root – use the dedicated system user. - Limit Redis to localhost or a private VPC to avoid exposed ports.
- Enable
php-fpmsecurity.limit_extensions = .phpto prevent arbitrary file execution. - Set
disable_functions = exec,passthru,shell_exec,systeminphp.iniunless absolutely required.
Bonus Performance Tips
- Enable
redis-cli --latencymonitoring – spikes > 5 ms usually indicate network contention. - Use
artisan horizonfor advanced queue metrics and auto‑scaling. - Compress Laravel views with
php artisan view:cacheafter each deploy. - Turn on MySQL query cache for read‑only tables (e.g.,
SET GLOBAL query_cache_type = ON;). - Deploy static assets via
npm run prodand serve them through Cloudflare edge.
FAQ
Q: My queue still exits with “Connection refused” after the fix.
A: Verify Redis is listening on 127.0.0.1:6379 and that firewall-cmd --list-all isn’t blocking it.
Q: Can I use MySQL as the queue driver?
Yes, but expect higher lock contention on a busy VPS. Switch to Redis or Amazon SQS for production workloads.
Q: Do I need Supervisor on a shared cPanel host?
Not if you have only a few workers. Use a cron entry that runs php artisan queue:work --stop-when-empty every minute.
Final Thoughts
Queue crashes on a cPanel VPS are rarely a Laravel bug; they’re a server‑configuration symptom. By eliminating MySQL lock bottlenecks, aligning file permissions, and giving Supervisor and PHP‑FPM the resources they need, you transform a flaky midnight cron into a rock‑solid, scalable backbone for any SaaS or WordPress‑integrated product.
Take the time now to implement the steps above—your future self (and your customers) will thank you.
Looking for Cheap, Secure Hosting?
If you prefer a provider that handles the VPS plumbing for you, try Hostinger’s managed VPS plans. They offer pre‑configured PHP‑FPM, Redis, and one‑click Laravel installers at a fraction of the cost of traditional cloud providers.
No comments:
Post a Comment