0% прочитано

Laravel Supervisor: SSR, Queue Workers, and Reverb on One Production Server

Several long-running processes operate together in ICanUp production: a Laravel queue worker, Reverb, and Inertia SSR. I use Supervisor not merely to start commands but as the lifecycle contract for these processes: automatic startup, restart after failure, separate logs, status control, and predictable behavior after deployment.

15 вересня 2026 р. 10 хв читанняDevOps

Once ICanUp gained Reverb, background jobs, and Inertia SSR, PHP-FPM alone was no longer enough to represent the whole production application.

Three separate long-running processes need to stay alive: the queue worker, Reverb, and SSR.

Each can fail independently. More importantly, losing one of them does not always make the entire website obviously unavailable.

That is why I use Supervisor not merely as a convenient startup command but as the lifecycle contract for long-running application processes.

Supervisor manages process lifecycle: startup, restart after failure, stopping, logs, and status. A RUNNING status still does not prove that the service is functionally healthy.
Laravel production server with Supervisor managing queue worker, Reverb and Inertia SSR as separate long-running processes
One production server, three independent long-running processes. Supervisor manages their lifecycle, while each service still needs its own health verification.

One Laravel Application Does Not Mean One Process

PHP-FPM handles HTTP requests in my production environment, but part of the application operates outside the normal HTTP request-response cycle.

The queue needs to process background jobs. Reverb needs to maintain WebSocket connections. SSR needs to be ready to render Inertia pages on the server.

These are separate processes with different commands, failure modes, and verification methods.

Process

Responsibility

What a user may notice when it fails

Laravel queue worker

Background jobs

The site may still load while jobs accumulate or execute late

Reverb

WebSocket and realtime events

HTTP still works, but live updates stop arriving

Inertia SSR

Server rendering for Vue/Inertia

A page may still load while SSR or the SEO response becomes incorrect

That Is What Makes Partial Failure Easy to Miss

When PHP-FPM fails, the problem is obvious because HTTP requests stop being processed correctly.

Long-running process failures are less obvious.

A stopped queue worker can remain unnoticed for minutes or hours. Reverb can fail while normal navigation remains healthy. SSR can fail only on one page or one rendering path.

"The site returns 200" and "all application processes are healthy" are different statements.

I Run Three Separate Supervisor Programs in Production

In the current ICanUp production environment, Supervisor manages separate processes for the worker, Reverb, and SSR.

The names are deliberately independent so each service can be checked or restarted separately.

Production processes
icanup-worker:icanup-worker_00icanup-reverbicanup-ssr

This separation matters. I do not want to restart the entire application stack because only one process needs attention.

My First Post-Deployment Command Is a Real Status Check

BASH - Check all Supervisor processes
sudo supervisorctl status

I expect the required processes to be in RUNNING state.

That is only the first verification layer.

Expected overall state
icanup-worker:icanup-worker_00   RUNNINGicanup-reverb                    RUNNINGicanup-ssr                       RUNNING

RUNNING Only Means the Process Has Not Exited

Supervisor answers the question: "Is the process alive?" very well.

It cannot always answer: "Is this process doing its job correctly?"

Supervisor shows

What still needs separate verification

worker = RUNNING

Whether queued jobs are actually being processed

Reverb = RUNNING

Whether the expected port is listening and WebSocket connections work

SSR = RUNNING

Whether a real page can complete server rendering

Supervisor is a process manager, not a complete application health system. Process status and service health should remain separate concepts.

The Queue Worker Is a Long-Running PHP Process

A Laravel queue worker does not exit after one job. It remains in memory and waits for more work.

After a new deployment, it can therefore continue running with previously loaded code until it is restarted correctly.

BASH - Ask Laravel workers to exit gracefully
php artisan queue:restart

queue:restart tells workers to exit after their current work. Supervisor can then start them again.

During deployment I can also explicitly restart or verify the Supervisor group.

BASH - Restart production workers
sudo supervisorctl restart icanup-worker:*

Reverb Lives Outside Normal HTTP Requests

Reverb maintains long-lived WebSocket connections.

Its failure does not necessarily affect a normal HTTP request. A visitor may open a page while realtime features have already stopped working.

That is why Reverb has its own Supervisor program.

BASH - Restart Reverb
sudo supervisorctl restart icanup-reverb

After restart, I verify more than Supervisor status. I also check that the server is actually listening on the expected ports.

BASH - Check listening ports
ss -lnt
I do not hardcode the Reverb port into this general example. It belongs to environment configuration and should be checked against the active production settings.

SSR Also Needs to Stay Alive

Adding Inertia SSR introduced another long-running process.

Its job is to accept rendering requests from Laravel and return server-rendered HTML for Inertia pages.

If the SSR process stops or begins failing, the failure may not look like a complete HTTP outage.

BASH - Restart SSR
sudo supervisorctl restart icanup-ssr

I also verify a real page response. This is how ICanUp previously exposed the difference between metadata that existed only in the browser and metadata present in the initial SSR HTML.

I described that case separately in my article about Inertia SSR and metadata in the initial HTML response.

BASH - Verify SSR in the real HTTP response
curl -s https://icanup.com.ua/en/ \  | grep -E '<title>|og:title'

Each Role Should Be a Separate Process

I do not combine the worker, Reverb, and SSR into one shell script that Supervisor sees as a single process.

If a combined process partially fails, it becomes harder to identify the failing role, restart only what is needed, and keep useful logs.

One process per role
Supervisor|+-- icanup-worker|+-- icanup-reverb|+-- icanup-ssr

Configuration Should Define Lifecycle, Not Only the Command

For me, Supervisor configuration is more than command=....

I also want the working directory, process user, startup behavior, failure behavior, and shutdown semantics to be explicit.

INI - Simplified long-running process contract
[program:example-process]directory=/var/www/icanupcommand=<process-command>user=deploy autostart=trueautorestart=true startsecs=5stopsignal=TERMstopasgroup=truekillasgroup=true redirect_stderr=truestdout_logfile=<process-log>
This is a shortened lifecycle example, not a literal copy of the production config. Exact commands and log paths depend on the process role.

Why I Use autorestart

A long-running process can fail long after deployment has finished.

A runtime error, resource problem, or unexpected exception can terminate it later.

autorestart=true allows Supervisor to start it again without manual intervention.

An endless restart loop is not a fix, however. A repeatedly crashing process still needs log inspection and diagnosis.

The Process User Matters as Much as the Command

In my production environment, long-running CLI and SSR processes can run as deploy, while HTTP requests through PHP-FPM run as www-data.

That creates a separate filesystem permission contract.

I do not run application processes as root merely to bypass permission problems.

If several system users write shared files, permissions need to be designed explicitly. Supervisor does not repair an incorrect permission model automatically.

Changing Supervisor Config Requires More Than restart

When Supervisor configuration itself changes, I first ask Supervisor to reread it.

BASH - Apply changed Supervisor configuration
sudo supervisorctl rereadsudo supervisorctl updatesudo supervisorctl status

reread discovers configuration changes, while update applies the new program set.

That is different from restarting a process Supervisor already knows about.

Long-Running Processes Need the New Code After Deployment

Deployment is one of the main reasons I manage these processes through Supervisor.

After updating the Git revision, Composer dependencies, frontend build, or environment configuration, an old process may still remain in memory with old state.

A deployment therefore does not end when files are updated.

What happens after code changes
Deploy new code      |      vRebuild dependencies / caches      |      vRestart long-running processes      |      +-- Queue worker      +-- Reverb      +-- SSR      |      vVerify process state      |      vVerify real service behavior

My Post-Deployment Order

  • Update application code and dependencies.
  • Run migrations and rebuild Laravel caches.
  • Ask Laravel queue workers to exit gracefully.
  • Restart the worker group under Supervisor.
  • Restart Reverb.
  • Restart SSR.
  • Verify supervisorctl status.
  • Verify listening ports for network services.
  • Verify real HTTP and SSR output.
  • Only then consider the deployment successful.

This Belongs in CI/CD, Not in a Manual Memory Checklist

I do not want to remember these steps manually on every deployment.

In ICanUp, restarting and verifying long-running processes is part of the deployment flow.

I described the broader setup in my article about a real Laravel CI/CD pipeline.

The Three Processes Do Not Scale the Same Way

Queue workers can naturally have multiple processes when job volume grows.

Supervisor supports process groups and multiple instances for that purpose.

Reverb and SSR on a single server have different constraints. Without an architectural reason, I do not start random duplicate processes that compete for the same port or endpoint.

Do not manually start a second Reverb or SSR process on top of one already managed by Supervisor. Check supervisorctl status first and restart the managed process when appropriate.

Separate Logs Make Failures Easier to Diagnose

If the worker, Reverb, and SSR all write into one undifferentiated process log, every incident begins with identifying which service produced the message.

Separate logs make the relationship clearer:

Separate logs by process role
worker failure -> worker logReverb failure -> Reverb logSSR failure -> SSR log

Laravel application logging remains a separate layer. Supervisor logs and Laravel logs answer different questions.

What I Check When a Process Keeps Restarting

  • Supervisor command and working directory.
  • The user that runs the process.
  • Environment variables available to the process.
  • Permissions for required files and directories.
  • The dedicated process log.
  • Laravel log when the process boots Laravel.
  • Whether the required port is already in use.
  • Whether the same command works manually as the same system user.

Manual Verification Must Reproduce the Supervisor Context

A command working in my SSH shell does not prove it works under Supervisor.

The process can have a different user, working directory, environment, and PATH.

During diagnosis I therefore try to reproduce the same context Supervisor uses.

A command that works manually and a process Supervisor can keep stable in production are not always the same thing.

Supervisor Does Not Replace Health Checks

I deliberately separate process supervision from health verification.

Supervisor should keep the process alive. Deployment verification should prove that the process actually performs its function.

Layer

Example verification

Process

supervisorctl status

Network

Expected port is listening

Application

A real job or SSR render succeeds

Public behavior

The user-facing feature works

The Worst Failure Is a Silent Partial Failure

The most dangerous situation for me is not a complete outage but a site that only looks healthy.

HTTP responds while the queue worker is stalled. A page opens while realtime is gone. The browser displays content while SSR fails and the initial HTML is wrong.

That is why the three long-running processes need independent management and independent verification.

What I Do Not Do

  • Do not run the worker, Reverb, and SSR as one combined shell process.
  • Do not run application processes as root for convenience.
  • Do not treat RUNNING as sufficient proof of service health.
  • Do not manually start a second Reverb before checking Supervisor.
  • Do not leave long-running processes on old code after deployment.
  • Do not combine all process-role logs into one stream without a reason.
  • Do not reboot the whole server merely because one application process failed.
  • Do not verify production with only one HTTP 200 response.

My Short Supervisor Checklist

  • Every long-running role has its own Supervisor program.
  • Each program has the correct working directory.
  • The process runs as the intended system user.
  • autostart is enabled and autorestart behavior is intentional.
  • Shutdown signals are propagated to child processes correctly.
  • Worker, Reverb, and SSR have separate logs.
  • After Supervisor config changes, run reread and update.
  • After deployment, long-running processes are restarted.
  • After restart, verify supervisorctl status.
  • After process verification, run a separate functional check.

What I Kept for Next Time

  • Separate the HTTP runtime from long-running processes.
  • Give the worker, Reverb, and SSR independent lifecycles.
  • Do not confuse process status with service health.
  • Refresh long-running processes after deployment.
  • Verify Reverb through both process and network behavior.
  • Verify SSR through a real HTML response.
  • Verify the queue worker through actual job processing, not only RUNNING state.
  • Do not hide permission or environment problems by running as root.
  • Keep services independent enough that one failure does not require restarting the whole application stack.

Conclusion

On one ICanUp production server, different parts of the same Laravel application run at the same time: PHP-FPM, a queue worker, Reverb, and Inertia SSR.

Supervisor gives me one clear way to manage the three long-running processes: start them after reboot, restart them after failure, refresh them after deployment, and inspect their current state.

The most important lesson is different, however: process management and functional health verification are separate layers.

A worker can be RUNNING without doing useful work. Reverb can be RUNNING while realtime still needs verification. SSR can be RUNNING while a specific page still fails during server rendering.

My production contract is therefore simple: Supervisor keeps processes alive, while deployment and health checks prove that they are actually doing their job.