Skip to main content

Swoole and RoadRunner: Running PHP as a Long-Lived Process

PHP · 29.09.2026

Classic PHP-FPM starts the interpreter fresh on every request: boots the application, handles one request, and drops everything from memory. Swoole and RoadRunner work differently — the application loads once and stays in memory between requests, similar to Node.js or Go. This speeds processing up several times over, but it requires a different approach to writing code.

How this differs from familiar PHP-FPM

In the FPM model, every request gets a clean state: no leaks between users, but the dependency container, database connection, and class autoloading are all reinitialized from scratch every time. In the Swoole or RoadRunner model, initialization happens once when the worker starts, and after that only the handler code runs on each request — that is where most of the speed gain comes from. The difference from a plain CLI run of PHP was covered in the article on PHP CLI and FPM.

The flip side is global state. Static class properties, singletons, and simply forgotten variables in closures live on between requests and can leak memory or leak one user's data into another's response if the code was not written with a long-lived process in mind.

Installing Swoole as a PHP extension

sudo apt install php8.3-dev php-pear
sudo pecl install swoole
echo "extension=swoole.so" | sudo tee /etc/php/8.3/mods-available/swoole.ini
sudo phpenmod swoole
php -m | grep swoole

Swoole embeds directly into PHP as an extension and adds classes for an asynchronous HTTP server, coroutines, and websockets. For a separate websocket protocol in plain PHP, see the article on WebSockets with Ratchet.

RoadRunner as an alternative

RoadRunner is built differently: it is a separate Go binary that keeps a pool of PHP workers and talks to them over the Goridge protocol through stdin/stdout. The PHP code itself stays plain PHP with no special extension — you only need the spiral/roadrunner-http package through Composer, described in the article on installing Composer.

SettingSwooleRoadRunner
TypePHP extensionseparate Go binary
Coroutinesyes, built inno, workers are single-threaded
Laravel/Symfony compatibilitythrough separate packagesthrough separate packages
Learning curvehigherlower

Where memory leaks come from

A long-lived process makes leaks visible within minutes, not weeks, like in regular desktop applications. Check these spots:

  • Static arrays and class-level caches that grow with every request and are never cleared.
  • A database connection that gets reopened on every request instead of reusing the already open one.
  • The global dependency container — if it gets rebuilt on every request, the whole point of a long-lived process is lost.
  • Circular references between objects, which PHP's garbage collector does not always clean up in time inside a long process.

Monitoring workers and restarts

Even a correctly written application is worth restarting on a schedule, just like regular queues — the approach is almost the same as in the article on Supervisor for queues. Swoole has a built-in max_request setting that restarts a worker after a given number of processed requests:

$server->set([
    'worker_num' => 4,
    'max_request' => 5000,
    'task_worker_num' => 2,
]);

Checklist for moving to a long-lived process

  1. Remove any static properties from the code that accumulate data between requests without an explicit reset.
  2. Set up a scheduled worker restart through max_request in Swoole or an equivalent limit in RoadRunner.
  3. Run load testing before switching production over — rising latency tied to worker uptime will show a leak right away.
← Back to Knowledge Base Ask Support