spiral/roadrunner
RoadRunner is a high-performance PHP application server and process manager written in Go. It runs PHP apps as long-lived workers and supports plugin-based features like HTTP/2/3, HTTPS, and FastCGI with PSR-7/17 compatibility—an alternative to Nginx+FPM.
## Getting Started
### Minimal Setup
1. **Installation**:
```bash
composer require spiral/roadrunner-cli
./vendor/bin/rr get-binary
Ensure PHP extensions php-curl, php-zip, and php-sockets are enabled.
Configure .rr.yaml:
version: '3'
server:
command: "php worker.php"
http:
address: "0.0.0.0:8080"
Create a Worker (worker.php):
<?php
use Spiral\RoadRunner;
use Nyholm\Psr7;
$worker = RoadRunner\Worker::create();
$psrFactory = new Psr7\Factory\Psr17Factory();
$httpWorker = new RoadRunner\Http\PSR7Worker($worker, $psrFactory, $psrFactory, $psrFactory);
while ($req = $httpWorker->waitRequest()) {
$rsp = new Psr7\Response();
$rsp->getBody()->write('Hello RoadRunner!');
$httpWorker->respond($rsp);
}
Run:
./rr serve -c .rr.yaml
nginx + php-fpm with RoadRunner’s HTTP plugin for Laravel/Symfony apps.RoadRunner\Http\PSR7Worker with PSR-7 factories (e.g., nyholm/psr7).
$app = require __DIR__.'/bootstrap/app.php';
$worker = new RoadRunner\Http\PSR7Worker(
RoadRunner\Worker::create(),
$app->getPsr7Factory(),
$app->getPsr7Factory(),
$app->getPsr7Factory()
);
gzip, headers) in .rr.yaml:
http:
middleware: [gzip, headers]
worker.php:
$jobsWorker = new RoadRunner\Jobs\JobsWorker($worker);
while ($job = $jobsWorker->wait()) {
$result = dispatch($job->getPayload());
$jobsWorker->complete($job, $result);
}
.rr.yaml:
jobs:
pool:
num_workers: 4
queues: ["default"]
RoadRunner\Grpc\GrpcWorker with generated protobuf classes.
$grpcWorker = new RoadRunner\Grpc\GrpcWorker($worker, __DIR__.'/proto');
$grpcWorker->run();
.rr.yaml:
grpc:
proto: "proto/**/*.proto"
.rr.yaml:
http:
middleware: [otel]
otel.yaml (e.g., Jaeger, Prometheus).RoadRunner\Temporal\TemporalWorker for workflows:
$temporalWorker = new RoadRunner\Temporal\TemporalWorker($worker);
$temporalWorker->run();
.rr.yaml:
temporal:
connection: "temporal://localhost:7233"
server:
max_restarts: 5
restart_delay: 10s
EOF Errors:
php-sockets or incorrect worker command.php --modules | grep sockets and ensure server.command in .rr.yaml matches your worker script.Configuration Reloads:
.rr.yaml aren’t reflected without restart.SIGUSR2 (Unix) or rr reload (v2025.1.5+):
kill -USR2 $(pgrep rr)
gRPC Protobuf Paths:
**, {a,b}) require Go 1.25+..rr.yaml are correct:
grpc:
proto: "proto/**/*.proto"
KV Plugin Null Pointers:
v2025.1.1 or explicitly define a driver:
kv:
redis:
dsn: "redis://localhost"
Green Tea GC:
RR_GOGC=100 ./rr serve
Logs:
logs.level: debug in .rr.yaml for verbose output../rr logs -c .rr.yaml
Metrics:
http:
middleware: [prometheus]
http://localhost:8080/metrics.Worker Isolation:
server:
command_all: ["php worker-http.php", "php worker-jobs.php"]
Custom Middleware:
RoadRunner\Http\MiddlewareInterface and register in .rr.yaml:
http:
middleware: [custom_middleware]
Plugin Development:
Docker Optimization:
FROM ghcr.io/roadrunner-server/roadrunner:2025.X.X AS roadrunner
FROM php:8.3-cli
COPY --from=roadrunner /usr/bin/rr /usr/local/bin/rr
TLS Management:
rr tls generate (v2025.1.0+):
./rr tls generate --cert=cert.pem --key=key.pem
.rr.yaml:
http:
tls:
cert: "cert.pem"
key: "key.pem"
Worker Pool:
jobs.pool.num_workers based on CPU cores (e.g., 4 for 4 cores).HTTP Keep-Alive:
.rr.yaml:
http:
keep_alive: true
gRPC Compression:
protobuf extension for faster serialization:
docker-php-ext-install protobuf
Early Hints (103):
http:
early_hints: true
CVE Mitigations:
./rr update) to patch CVEs (e.g., gRPC, Go stdlib).TLS:
.rr.yaml:
http:
tls:
cert: "cert.pem"
key: "key.pem"
min_version: "TLS12"
Proxy Headers:
proxy_ip_parser middleware:
http:
middleware: [proxy_ip_parser]
How can I help you explore Laravel packages today?