Stop cPanel “Hung Service Checks”: Fix Runaway Laravel Queue Workers Caused by Cron

Stop cPanel “Hung Service Checks”: Fix Runaway Laravel Queue Workers Caused by Cron

Slug: stop-cpanel-hung-service-checks-laravel-queue-workers
Meta Description: Seeing “Hung Service Checks” on cPanel/WHM with sky-high load averages? The culprit is often cron spawning unlimited Laravel queue:work processes. Here’s the exact fix with systemd or Supervisor, plus troubleshooting tips.
Reading Time: 7–9 minutes
Categories: DevOps, cPanel, Laravel, Performance
Tags: cPanel, WHM, chkservd, CloudLinux, PHP-FPM, Laravel, queues, cron, systemd, Supervisor


TL;DR

If your server is firing “Hung Service Checks” and showing massive load averages, you probably have a cron job starting a new php artisan queue:work every minute. That spawns hundreds of PHP processes and melts the CPU.

Fix:

  • Run queue workers under systemd (or Supervisor) so there’s a fixed number of long-running workers.
  • Add safety flags: --sleep=3 --tries=3 --max-jobs=100 --max-time=3600.

Keep only the scheduler cron:

* * * * * php /home/USERNAME/APP/artisan schedule:run >> /dev/null 2>&1

Symptoms You’ll Notice

  • cPanel/WHM notifications: Hung Service Checks.
  • Load averages like 233.78 403.89 355.68.
  • top or ps shows many PHP/CGI processes:
    artisan queue:work and artisan schedule:run.
  • Disk and memory look fine; CPU is pegged.
  • Services “seem up,” but the server feels frozen or timeouts occur.

Why This Happens

Laravel queues are designed to run as long-lived workers. If you start queue:work via cron every minute, each minute creates another worker without stopping the previous one. After hours, you have hundreds of workers competing for CPU—chkservd interprets the system as hung.


The Correct Architecture

  1. Queue workers should be managed by systemd (or Supervisor) as a fixed number of persistent processes.

Cron should run only the scheduler:

* * * * * php /home/USERNAME/APP/artisan schedule:run >> /dev/null 2>&1

Step-by-Step Fix (systemd)

Works great on CentOS/Alma/Rocky/CloudLinux with cPanel. Replace paths and PHP binary as needed.

1) Kill the Runaway Processes (Immediate Relief)

pkill -f "artisan queue:work"
pkill -f "artisan schedule:run"
systemctl restart httpd
systemctl restart php-fpm
systemctl restart mysql

2) Clean Up Your Crontab

  • Remove any lines that start queue:work.

Keep only the scheduler:

* * * * * php /home/USERNAME/APP/artisan schedule:run >> /dev/null 2>&1

3) Create a systemd Service for Workers

Create /etc/systemd/system/laravel-worker.service:

[Unit]
Description=Laravel Queue Worker for myapp.example.com
After=network.target

[Service]
User=USERNAME
Group=USERNAME
WorkingDirectory=/home/USERNAME/APP
ExecStart=/opt/cpanel/ea-php84/root/usr/bin/php /home/USERNAME/APP/artisan queue:work --sleep=3 --tries=3 --max-jobs=100 --max-time=3600
Restart=always
RestartSec=5
StandardOutput=null
StandardError=null

[Install]
WantedBy=multi-user.target

Then enable it:

systemctl daemon-reload
systemctl enable laravel-worker
systemctl start laravel-worker
systemctl status laravel-worker

4) (Optional) Separate Queues by Priority

Create additional services, e.g. /etc/systemd/system/laravel-worker-mail.service:

ExecStart=/opt/cpanel/ea-php84/root/usr/bin/php /home/USERNAME/APP/artisan queue:work --queue=mail --sleep=3 --tries=3 --max-jobs=100 --max-time=3600

Start and enable them as above. This isolates critical queues (email, sms) from heavy jobs (thumbnails, reports).


Alternative: Supervisor (If You Prefer)

Install & Configure (paths may vary under CloudLinux):

Create /etc/supervisord.d/laravel-worker.ini:

[program:laravel-worker]
directory=/home/USERNAME/APP
command=/opt/cpanel/ea-php84/root/usr/bin/php artisan queue:work --sleep=3 --tries=3 --max-jobs=100 --max-time=3600
autostart=true
autorestart=true
user=USERNAME
numprocs=1
redirect_stderr=true
stdout_logfile=/home/USERNAME/APP/storage/logs/worker.log

Reload and start:

supervisorctl reread
supervisorctl update
supervisorctl status

Scale by increasing numprocs gradually and watching load.


Performance & Safety Tips

  • Use Redis for queues where possible (faster and more resilient than database-backed queues).
  • Limit concurrency: start with 1 worker; add more only if CPU headroom exists.
  • Use flags wisely:
    • --sleep=3: pause when the queue is empty.
    • --tries=3: fail fast on bad jobs.
    • --max-jobs=100 & --max-time=3600: recycle workers to prevent memory leaks.
  • Monitor:
    • htop, atop, or glances for live CPU/mem.
    • iostat -x 1 for disk IO.
    • Laravel Horizon for queue visibility (if using Redis).

Cache everything:

php artisan optimize
php artisan config:cache
php artisan route:cache
php artisan view:cache

Troubleshooting Checklist

  • ✅ Crontab has no queue:work lines.
  • ✅ Only one scheduler cron per app.
  • ✅ systemd/Supervisor shows the intended number of workers.
  • ✅ Load averages settle close to your core count.
  • storage/logs/ contains no recurring fatal errors.
  • ✅ Horizon (if used) shows steady throughput and no stuck jobs.

FAQ

Q: Can I run multiple workers?
A: Yes—but scale carefully. Start with 1, then add more services or increase numprocs while watching load.

Q: Why not cron for queue:work?
A: Cron is designed for short-lived tasks. Queue workers are long-lived. Cron will spawn a new process every run, causing pile-ups.

Q: Do I need Horizon?
A: Not required, but recommended if you use Redis. It gives great insight into job throughput and failures.

Q: My load is still high after the fix—now what?
A: Profile your jobs (optimize code, batch work), add caching, upgrade CPU, or split heavy jobs onto a separate worker box.


Wrap up

Hung Service Checks” on cPanel usually means the CPU is overwhelmed—often by cron repeatedly starting new Laravel workers. The permanent solution is simple: scheduler in cron, workers under systemd or Supervisor, and sensible worker limits. Do that, and your server will breathe again.


Read more