This is a submission for DEV's Summer Bug Smash: Clear the Lineup powered by Sentry.
Project Overview
I was working on a Laravel application running on a cPanel server where background processing was handled through Laravel queues and Redis.
The application also had an integration with Make.com. Make.com periodically sent requests to the Laravel application, which then dispatched background jobs for processing.
The overall flow looked like this:
Make.com
|
| API requests
v
Laravel application
|
v
Redis
|
v
Laravel Queue Worker
|
v
Background processing
The setup worked, but under certain conditions the queue would stop processing and eventually the Make.com requests would start failing.
At first, I thought I had a Laravel queue problem.
It turned out to be a much more interesting infrastructure problem.
Bug Fix or Performance Improvement
The first problem: nohup was not enough
Initially, I was running the Laravel queue worker using nohup.
The idea was simple: start the worker in the background and let it continue running after the terminal session ended.
That worked until the server or cPanel environment restarted.
After a restart, the nohup process was gone.
So the situation became:
Server restart
|
v
nohup queue worker disappears
|
v
Redis still contains jobs
|
v
No worker is processing them
|
v
Queue starts growing
I had to manually restart the worker.
That was the first problem I fixed.
Moving the worker to cPanel Cron
Instead of depending on a manually started long-running process, I changed the setup so cPanel Cron would execute the queue worker.
The important part of the command was:
cd /home/user/laravel && \
/bin/flock -n storage/framework/queue-worker.lock \
php artisan queue:work redis \
--queue=default \
--stop-when-empty \
--sleep=3 \
--tries=3 \
--timeout=120 \
--max-time=120
Using flock was important because I did not want multiple Cron executions accidentally starting competing workers.
--stop-when-empty also made sense for this Cron-based model: the worker could process the available jobs and exit instead of relying on a permanently running process.
At this point, the queue was much more resilient.
But I had not solved the entire problem yet.
The second problem: Make.com requests started timing out
After moving the queue execution to Cron, I noticed another failure mode.
When the server became unhealthy or PHP requests became slow, Make.com started reporting high request timeouts.
This was confusing because Redis itself was working.
The queue connection was working.
The Laravel application could communicate with Redis.
So I started looking at the server rather than assuming the queue was broken again.
The investigation eventually led me to PHP-FPM.
Finding the PHP-FPM bottleneck
The server had approximately:
RAM: ~65 GB
CPU: 20 cores
But PHP-FPM had very conservative worker settings.
The configuration I found was:
pm.max_children = 5
pm.max_requests = 20
pm.process_idle_timeout = 10
That immediately became interesting.
The server had a lot of available memory, but PHP-FPM was configured to allow only a small number of child processes.
Under concurrent requests, that meant requests could wait for an available PHP-FPM worker.
The simplified flow looked like this:
Multiple requests arrive
|
v
PHP-FPM
|
+--> Worker 1
+--> Worker 2
+--> Worker 3
+--> Worker 4
+--> Worker 5
|
v
Additional requests wait
|
v
Request latency increases
|
v
Make.com timeout
The important lesson was that a server having plenty of RAM does not automatically mean the application is configured to use its available resources effectively.
GIF: The original failure
Show a short recording of:
- Make.com scenario starting
- HTTP request being sent
- Long wait
- Request timeout/failure
- Laravel/server becoming slow
Code
Before: PHP-FPM configuration
The original settings were:
pm.max_children = 5
pm.max_requests = 20
pm.process_idle_timeout = 10
After: PHP-FPM configuration
I increased the PHP-FPM capacity after checking the server resources and workload.
pm.max_children = [YOUR_ACTUAL_FINAL_VALUE]
pm.max_requests = [YOUR_ACTUAL_FINAL_VALUE]
pm.process_idle_timeout = [YOUR_ACTUAL_FINAL_VALUE]
Replace the values above with the exact production values you actually deployed. I am intentionally not inventing them here.
The important change was not simply "make the numbers bigger."
The goal was to match PHP-FPM's process capacity to the actual server resources and workload instead of running a 65 GB / 20-core server with extremely conservative worker limits.
Queue worker configuration
I also changed the queue execution model from a manually started nohup process to a Cron-managed worker.
cd /home/user/laravel-main && \
/bin/flock -n storage/framework/queue-worker.lock \
php artisan queue:work redis \
--queue=default \
--stop-when-empty \
--sleep=3 \
--tries=3 \
--timeout=120 \
--max-time=120
The lock prevents overlapping Cron executions from starting multiple copies of the same worker.
GIF: Queue recovery
Show:
Cron
↓
Laravel queue:work
↓
Redis
↓
Job processing
↓
Queue becomes empty
My Improvements
The biggest improvement was not a single configuration value.
It was changing how I diagnosed the problem.
Initially, my mental model was:
Make.com request fails
|
v
Laravel queue is broken
That turned out to be too narrow.
I started checking each layer separately:
Make.com
|
v
Laravel API
|
v
PHP-FPM
|
v
Redis
|
v
Queue Worker
|
v
Background Job
This helped separate two different problems.
Problem 1
The queue worker was dependent on nohup.
A server restart killed the worker.
Fix
Move queue execution to cPanel Cron and use flock to prevent overlapping workers.
Problem 2
The server could become slow enough that Make.com API requests timed out.
Investigation
Redis was reachable.
The queue connection worked.
The server had significant unused RAM.
PHP-FPM, however, had very conservative process limits.
Fix
Tune PHP-FPM according to the actual server capacity and workload.
GIF: Finding the real bottleneck
A useful GIF here would show:
BEFORE
pm.max_children = 5
pm.max_requests = 20
pm.process_idle_timeout = 10
↓
Server investigation
↓
AFTER
pm.max_children = [ACTUAL VALUE]
pm.max_requests = [ACTUAL VALUE]
pm.process_idle_timeout = [ACTUAL VALUE]
What I learned
The biggest lesson from this bug was:
A queue problem isn't always a queue problem.
When a background job stops processing, it is tempting to immediately investigate Redis or Laravel's queue configuration.
But the complete path matters.
A queue-based application depends on multiple layers:
External automation
↓
HTTP server
↓
PHP-FPM
↓
Laravel
↓
Redis
↓
Queue worker
↓
Job
If any layer becomes a bottleneck, the symptom can appear somewhere completely different.
In my case, the symptoms looked like:
Make.com
↓
Timeout
and:
Laravel
↓
Queue not processing
But the investigation revealed two separate reliability issues:
1. Worker lifecycle
└── nohup process disappeared after restart
2. Request capacity
└── PHP-FPM worker configuration was too conservative
Fixing both made the system considerably more resilient.
Before vs After
Before
Make.com
|
v
Laravel API
|
v
PHP-FPM
|
| Only a small number of workers
v
Requests wait / become slow
|
v
Make.com timeout
Queue:
nohup
|
v
Queue worker
|
X
Server restart
|
v
Worker disappears
After
Make.com
|
v
Laravel API
|
v
PHP-FPM
|
v
Laravel
|
v
Redis
|
v
Cron-managed queue worker
|
v
Background processing
The infrastructure became easier to reason about because the queue worker no longer depended on a manually maintained nohup process.
Results
After the changes, the queue worker no longer depended on manually restarting nohup after a server restart.
The PHP-FPM configuration was also adjusted to better match the server's available resources and the application's workload.
Most importantly, I stopped treating the queue as an isolated component.
The queue, PHP-FPM, Laravel API, Redis, Cron, and external automation were all part of the same system.
That was the real bug to fix.
Top comments (0)