PHP Workers in RoadRunner: Configuration and Usage Guide

PHP Workers

To execute your PHP application, you need to create a worker endpoint and configure RoadRunner to utilize it. First, install the required packages using Composer:

composer require spiral/roadrunner nyholm/psr7

The simplest entry point using the PSR-7 server API looks like this:

<?php

use Spiral\RoadRunner;
use Nyholm\Psr7;

include "vendor/autoload.php";

$worker = RoadRunner\Worker::create();
$psrFactory = new Psr7\Factory\Psr17Factory();

$worker = new RoadRunner\Http\PSR7Worker($worker, $psrFactory, $psrFactory, $psrFactory);

while ($req = $worker->waitRequest()) {
    try {
        $response = new Psr7\Response();
        $response->getBody()->write('Hello world!');

        $worker->respond($response);
    } catch (\Throwable $e) {
        $worker->getWorker()->error((string)$e);
    }
}

The worker expects to communicate with the parent RoadRunner server through standard pipes. Create a .rr.yaml configuration file to enable it:

server:
  command: "php psr-worker.php"

http:
  address: 0.0.0.0:8080
  pool:
    num_workers: 4

If you prefer JSON over YAML, use .rr.json instead:

{
  "server": {
    "command": "path-to-php/php psr-worker.php"
  },
  "http": {
    "address": "0.0.0.0:8080",
    "pool": {
      "num_workers": 4
    }
  }
}

Start the application by downloading the RR binary and running rr serve.

Alternative Communication Methods

PHP Workers use standard pipes (STDOUT and STDERR) to exchange data frames with the RR server. In some cases, you may want alternative communication methods such as TCP sockets:

server:
  command: "php psr-worker.php"
  relay: "tcp://localhost:7000"

http:
  address: 0.0.0.0:8080
  pool:
    num_workers: 4

Or Unix sockets:

server:
  command: "php psr-worker.php"
  relay:  "unix://rr.sock"

http:
  address: 0.0.0.0:8080
  pool:
    num_workers: 4

Troubleshooting

In certain situations, RR may fail to handle errors produced by PHP workers (PHP missing, script deadlocks, etc.).

$ rr serve
DEBU[0003] [rpc]: started
DEBU[0003] [http]: started
ERRO[0003] [http]: unable to connect to worker: unexpected response, header is missing: exit status 1
DEBU[0003] [rpc]: stopped

You can resolve this by manually invoking the command set in the .rr file:

$ php psr-worker.php

Before providing any input, the worker should not cause any errors and must fail with an invalid input signature after the first input character.

Other Worker Types

Different RoadRunner implementations may define their own worker APIs, such as gRPC and Workflow/Activity Workers.

Environment Configuration

All RoadRunner workers inherit system configuration available to the parent server process. Additionally, you can customize ENV variables passed to workers using the env section in your .rr configuration file:

server:
  command: "php worker.php"
  env:
     key: value

All keys will be automatically uppercase!

Default ENV Values

RoadRunner provides a set of ENV values to help PHP processes identify how to communicate correctly with the server:

Key Description
RR_MODE Determines the mode the worker should use ("http", "temporal")
RR_RPC Contains RPC connection address when enabled
RR_RELAY "pipe" or "tcp://..." depending on server relay configuration

Developer Mode

RoadRunner runs PHP scripts in daemon mode, meaning the server must be reloaded every time you change the codebase.

If you use any modern IDE, you can implement this by adding a file watcher that automatically calls rr reset for the plugin specified in the reload configuration.

Alternatively, use the auto-resetter.

Runing in Docker

You can reset the RR process by connecting to docker using a local RR client:

rpc:
  listen: tcp://:6001

Ensure you forward/expose port 6001.

Then run rr reset local when file changes occur.

Debug Mode

Run workers in debug mode (similar to PHP-FPM):

http:
  pool.debug: true

Error Handling

There are several methods to handle errors produced by PHP workers.

The simplest and most common approach is to respond to the parent service using getWorker()->error():

try {
    $resp = new \Zend\Diactoros\Response();
    $resp->getBody()->write("hello world");

    $psr7->respond($resp);
} catch (\Throwable $e) {
    $psr7->getWorker()->error((string)$e);
}

You can also flush warnings and errors to STDERR to output them directly to the console (similar to docker-compose):

file_put_contents('php://stderr', 'my message');

Since RoadRunner 2.0, all warnings sent to STDOUT are also forwarded to STDERR.

Restarting Workers

RoadRunner provides multiple methods to safely restart workers on demand. Both methods can be used on live servers without causing downtime.

Stop Command

You can send a stop command from the worker to the parent server to force process destruction. In this case, jobs/requests are automatically forwarded to the next worker.

We can demonstrate this by implementing control via max_jobs on the PHP side:

<?php

use Spiral\RoadRunner;
use Nyholm\Psr7;

include "vendor/autoload.php";

$worker = RoadRunner\Worker::create();
$psrFactory = new Psr7\Factory\Psr17Factory();

$worker = new RoadRunner\Http\PSR7Worker($worker, $psrFactory, $psrFactory, $psrFactory);

$count = 0;
while ($req = $worker->waitRequest()) {
    try {
        $response = new Psr7\Response();
        $response->getBody()->write('Hello world!');

        $count++;
        if ($count > 10) {
            $worker->getWorker()->stop();
            return;
        }

        $worker->respond($response);
    } catch (\Throwable $e) {
        $worker->getWorker()->error((string)$e);
    }
}

This method can be used to control memory usage within PHP scripts.

Full Reset

You can also trigger reconstruction of all RoadRunner workers using the embedded RPC bus:

$rpc = \Spiral\Goridge\RPC\RPC::create('tcp://127.0.0.1:6001');
$rpc->call('resetter.Reset', 'http');

Embedded Monitoring

RoadRunner can monitor your application and run soft resets when necessary (between requests). Previously named limit, now called supervisor.

Configuration

Edit your .rr file to specify application limits:

http:
  address: "0.0.0.0:8080"
  pool:
    num_workers: 6
    supervisor:
      # watch_tick defines how often to check the state of the workers (seconds)
      watch_tick: 1s
      # ttl defines maximum time worker is allowed to live (seconds)
      ttl: 0
      # idle_ttl defines maximum duration worker can spend in idle mode after first use. Disabled when 0 (seconds)
      idle_ttl: 10s
      # exec_ttl defines maximum lifetime per job (seconds)
      exec_ttl: 10s
      # max_worker_memory limits memory usage per worker (MB)
      max_worker_memory: 100

RPC to Application Server

You can connect to the application server via SocketRelay:

$rpc = \Spiral\Goridge\RPC\RPC::create('tcp://127.0.0.1:6001');

You can immediately use this RPC to call embedded RPC services, for example HTTP:

var_dump($rpc->call('informer.Workers', 'http'));

Note: When running workers in debug mode (http: { debug: true } in .rr.yaml), the number of http workers will be zero (returns an empty array). This behavior may change in the future and you should not rely on this result to check if RoadRunner started in development mode.

You can read how to create your own services and RPC methods in this section.

Important Notes

File Uploads

Since file uploads are handled on the RR side, the PHP process only receives the temporary file name. This resource is not registered in the uploaded files hash, therefore the is_uploaded_file function will always return false.

Reference: https://github.com/spiral/roadrunner/issues/133

Exit and Die Functions

Note that you should not use any of the following methods: die, exit. If your library needs to write content to standard output, use buffered output.

Caching

Using RoadRunner on Windows with the WinCache extension may cause worker bytecode to get stuck in memory.

Debugging

You can use RoadRunner scripts with the xDebug extension. To enable it, configure your IDE to accept remote connections.

Note: If you run multiple PHP processes, you must expand the maximum number of connections to match the number of active workers, otherwise some calls will not be caught at your breakpoints.

To activate xDebug, ensure xdebug.mode=debug is set in your php.ini.

To enable xDebug in your application, ensure you set the XDEBUG_SESSION ENV variable:

rpc:
   listen: tcp://127.0.0.1:6001

server:
   command: "php worker.php"
   env:
      XDEBUG_SESSION: 1

http:
   address: "0.0.0.0:8080"
   pool:
      num_workers: 1
      debug: true

You should now be able to use breakpoints and inspect state.

Tags: roadrunner PHP Golang psr-7 workers

Posted on Sat, 18 Jul 2026 16:24:08 +0000 by jronyagz