RoadRunner
Overview
RoadRunner is a high-performance PHP application server and process manager written in Go. Instead of re-bootstrapping the PHP interpreter on every request the way PHP-FPM does, it starts a pool of long-lived PHP worker processes once and keeps them warm. It then dispatches each incoming request to a worker over an efficient pipes relay. The result is lower per-request overhead, predictable memory use and built-in supervision, all from a single static binary that also adds HTTP/2, gRPC, background jobs and key-value plugins.
This SecureAnyCloud image runs RoadRunner on Ubuntu 24.04 on Azure. It includes a minimal demo PSR-7 worker application that is already vendored and running, so the whole stack works the moment the instance boots. RoadRunner binds only to loopback, and nginx on port 80 is the only way in from the network. nginx is ready for your TLS certificate.
What you get:
- RoadRunner 2025.1.15 (MIT), the official prebuilt Go binary at
/usr/local/bin/rr, SHA256-verified at build time - PHP 8.3 CLI with the extensions the workers need, plus Composer
- A demo PSR-7 worker application at
/var/www/roadrunner-demo, with its dependenciesspiral/roadrunner-httpandnyholm/psr7already vendored - Configuration at
/etc/roadrunner/.rr.yaml, with the HTTP, status and RPC listeners all bound to127.0.0.1 - The
roadrunner.servicesystemd unit, which starts on boot and runs as theroadrunnersystem user - An
nginx.servicereverse proxy on TCP 80 that forwards to the RoadRunner HTTP listener on127.0.0.1:8080 - The
roadrunner-firstboot.servicesystemd oneshot, which resolves the customer-facing URL and writes a service-info note on first boot - Listeners: TCP 80 (nginx, public),
127.0.0.1:8080(RoadRunner HTTP),127.0.0.1:2114(status and health),127.0.0.1:6001(RPC control channel) - An Ubuntu 24.04 LTS base with the latest security patches applied at build time
- The Azure Linux Agent for cloud integration and SSH key injection
- 24/7 SecureAnyCloud support with a guaranteed 24-hour response SLA
Prerequisites
- An active Azure subscription
- A subscription to the RoadRunner on Ubuntu 24.04 listing on Azure Marketplace
- An SSH key pair for administrative access
- Basic familiarity with the Linux command line, systemd and PHP
Deploy from the Azure Portal
Sign in to the Azure Portal. Search the Marketplace for RoadRunner on Ubuntu 24.04 LTS and select the SecureAnyCloud listing.
Choose Create, then select or create a resource group and region.
Pick a VM size. Standard_B2s (2 vCPU, 4 GiB) is a comfortable starting point for the demo application. For production workloads, scale up the size and the num_workers setting together.
Enter an administrative username (azureuser) and paste your SSH public key.
On the Networking tab, allow inbound TCP 22 (SSH) from your management IP and TCP 80 (HTTP) from the clients that will reach the application.
Review and create the VM. RoadRunner and nginx start automatically once the instance boots.
Deploy from the Azure CLI
Deploy the same image from the command line. Replace the values in angle brackets with your own, then open the HTTP port to the clients that need it.
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:roadrunner-ubuntu-24-04:default:latest
az vm create \
--resource-group my-roadrunner-rg \
--name my-roadrunner-vm \
--image sac1647283583153:roadrunner-ubuntu-24-04:default:latest \
--size Standard_B2s \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-sku Standard
az vm open-port --resource-group my-roadrunner-rg --name my-roadrunner-vm --port 80 --priority 900Connect over SSH
Connect to the instance as azureuser with your private key:
ssh azureuser@<vm-ip>Verify the RoadRunner service
RoadRunner and nginx are enabled and start on boot. Confirm that both units are active:
sudo systemctl start roadrunner nginx
sudo systemctl is-active roadrunner nginxBoth should report active. The image also writes a per-VM service-info note that you can read at any time:
sudo cat /root/roadrunner-info.txtConfirm the secure-by-default listeners
Every RoadRunner listener is bound to 127.0.0.1. Only nginx on port 80 faces the network. Confirm the binding:
sudo ss -tlnp | grep -E '127.0.0.1:(8080|2114|6001)|:80'The output lists 127.0.0.1:8080 (RoadRunner HTTP), 127.0.0.1:2114 (status and health), 127.0.0.1:6001 (RPC control channel) and nginx listening on port 80. Nothing exposes RoadRunner directly to the network. nginx is the single public entry point and is ready for your TLS certificate.
Check the RoadRunner version and health endpoint
Confirm the installed RoadRunner version, then query the built-in status plugin's health endpoint on loopback:
rr --version curl -s -o /dev/null -w 'health: HTTP %{http_code}\n' 'http://127.0.0.1:2114/health?plugin=http'The version prints as rr version 2025.1.15. The health endpoint returns HTTP 200 when the HTTP plugin has ready workers. Use this loopback endpoint for readiness and liveness checks.
Inspect the worker pool
RoadRunner keeps a pool of warm PHP workers. List them over the loopback RPC channel:
rr workers -c /etc/roadrunner/.rr.yamlThe output shows one row per worker (four by default) with its PID, status, number of executions, memory use and age. These are long-lived processes. RoadRunner dispatches requests to them and recycles a worker only after it hits max_jobs or the memory limit, which keeps per-request overhead low.
The demo application and warm-worker reuse
A minimal demo PSR-7 worker application is already running. It renders a small status page and a JSON health route. Confirm that it answers through nginx:
curl -s -o /dev/null -w 'GET / -> HTTP %{http_code}\n' http://localhost/
curl -s http://localhost/healthzThe /healthz route returns the serving worker's PID and a per-worker request counter. Call it several times and watch the counter climb on a reused worker. This shows that the interpreter stays warm between requests instead of restarting:
for i in 1 2 3 4 5; do curl -s http://localhost/healthz; echo; doneReview the configuration
The RoadRunner configuration lives at /etc/roadrunner/.rr.yaml. It holds no secrets and binds every listener to loopback:
cat /etc/roadrunner/.rr.yamlThe key settings are:
server.command: the PHP worker entry pointhttp.address: the loopback HTTP listener nginx proxies tohttp.pool.num_workers: how many warm workers to keepstatus.address: the loopback health endpointrpc.listen: the control channel used byrr workersandrr reset
Replace the demo app with your own PSR-7 application
The demo worker is a starting point. To run your own application, replace the worker entry point, or point server.command in /etc/roadrunner/.rr.yaml at your framework's RoadRunner worker. Then reload RoadRunner. Most modern PHP frameworks ship a RoadRunner integration.
Copy your worker in and vendor its dependencies with Composer:
sudo cp your-worker.php /var/www/roadrunner-demo/worker.php cd /var/www/roadrunner-demo && sudo -u roadrunner composer require your/frameworkThen reload the workers gracefully, with zero downtime. Running requests finish on the old workers while new requests go to fresh ones:
sudo rr reset -c /etc/roadrunner/.rr.yamlEnable HTTPS for your domain
nginx is the public entry point and is ready for TLS. Point a DNS record at the instance, then obtain a certificate for <your-domain>. Configure the nginx server block to terminate HTTPS while it continues to proxy to 127.0.0.1:8080.
A typical approach uses the nginx plugin for a Let's Encrypt client:
sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d <your-domain>Certbot edits the nginx configuration to add the TLS listener and sets up automatic renewal. RoadRunner itself is unchanged. It keeps serving the application on loopback while nginx terminates TLS.
Managing the service
systemd manages RoadRunner. Check the service status:
sudo systemctl status roadrunner --no-pager --lines=0To reload after a configuration or application change without dropping connections, prefer rr reset over a full restart, as in Step 10. It recycles the worker pool gracefully while the server keeps accepting requests.
To stop, start or restart the service, or to follow the structured JSON logs when troubleshooting, use the standard systemd commands:
sudo systemctl restart roadrunner
sudo journalctl -u roadrunner -fTroubleshooting
The application returns 502 through nginx. RoadRunner may not be running. Check sudo systemctl status roadrunner and the journal with sudo journalctl -u roadrunner -n 50 --no-pager. A PHP fatal error in the worker shows up there.
rr workers shows no workers. The workers failed to boot. Run sudo -u roadrunner php /var/www/roadrunner-demo/worker.php briefly to surface a PHP error, or check the journal.
The health endpoint returns 500. The HTTP plugin has no ready workers. Inspect the journal and confirm that server.command in /etc/roadrunner/.rr.yaml points at a valid worker script.
A worker leaks memory. Lower http.pool.supervisor.max_worker_memory in /etc/roadrunner/.rr.yaml so RoadRunner recycles the worker sooner. Then run sudo rr reset -c /etc/roadrunner/.rr.yaml.
Security recommendations
- Keep every RoadRunner listener on loopback (the default) and expose the application only through nginx.
- Terminate TLS at nginx (Step 11) and restrict the inbound port 80 and 443 rules to the clients that need them.
- Restrict inbound SSH (TCP 22) to your management IP range in the Azure network security group.
- Apply OS updates regularly with
sudo apt-get update && sudo apt-get upgrade. - Run your application code as the unprivileged
roadrunnersystem user (the default), not as root.
Support and Licensing
RoadRunner is distributed under the MIT license. This SecureAnyCloud image is a repackaged open source product, with additional charges for SecureAnyCloud support services.
All product and company names are trademarks or registered trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them.
Deploy on Azure
Find this image on the Azure Marketplace and deploy it in minutes. SecureAnyCloud maintains the image with security patches and provides a paired deployment guide for every release.
Need Help?
SecureAnyCloud provides 24/7 support with a guaranteed 24-hour response SLA. Contact support through the details on your Azure Marketplace listing.
Deploy on Azure
Launch RoadRunner on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.
View this image
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com
Overview
RoadRunner is a high-performance PHP application server and process manager written in Go. Instead of re-bootstrapping the PHP interpreter on every request the way PHP-FPM does, it starts a pool of long-lived PHP worker processes once and keeps them warm. It then dispatches each incoming request to a ready worker over an efficient pipes relay. The result is lower per-request overhead, predictable memory use, graceful worker recycling and built-in supervision. RoadRunner speaks PSR-7 and PSR-15 to your PHP application and adds HTTP, HTTP/2, gRPC, background jobs, queues and key-value plugins from a single static binary.
This SecureAnyCloud image is ready to run, so you can prove the stack end to end within seconds of launch.
What you get:
- RoadRunner v2025.1.15, installed as the official prebuilt Go binary at
/usr/local/bin/rr - PHP 8.3 and Composer, already installed
- A minimal demo PSR-7 worker application, vendored and running
- An nginx reverse proxy on port 80 that serves RoadRunner to the network, while RoadRunner's own HTTP, status and RPC listeners bind only to loopback
- A headless runtime with no web admin interface and no default password. The demo page is served content only and exposes no privileged surface, so there is nothing to rotate. Administration is over SSH with your own key, plus the
rrCLI and the loopback health endpoint.
Prerequisites
Before you deploy this image, you need:
- An Amazon Web Services account where you can launch EC2 instances
- IAM permissions to launch instances, create security groups and subscribe to AWS Marketplace products
- An EC2 key pair in the target Region for SSH access to the instance
- A VPC and subnet in the target Region, with a security group that allows inbound port 22 from your management network and port 80 from the clients that will reach the application
- The AWS CLI (version 2) installed locally, if you plan to deploy from the command line
Launch the Instance from the AWS Marketplace
Sign in to the AWS Management Console, open the EC2 service, and select Launch instance. Under Application and OS Images, choose AWS Marketplace AMIs and search for RoadRunner. Select the SecureAnyCloud listing, choose Select, then choose Continue on the subscription summary.
Pick an instance type of m5.large or larger. Choose your EC2 key pair under Key pair (login). Under Network settings, select your VPC and subnet. Create or select a security group that opens port 22 from your management network and port 80 from the clients that will reach the application. Leave the root volume at the default size or larger.
Select Launch instance. First-boot initialization takes a few seconds after the instance state becomes Running and the status checks pass. A first-boot service resolves the instance's public address and starts RoadRunner and nginx.
Launch the Instance from the AWS CLI
The command below launches an instance from the SecureAnyCloud RoadRunner Marketplace AMI into an existing subnet and security group. Replace the values as follows:
- <ami-id>: the AMI ID shown on the Marketplace listing
- <key-name>: your EC2 key pair name
- <subnet-id>: your subnet ID
- <security-group-id>: a security group that opens ports 22 and 80
aws ec2 run-instances \
--image-id <ami-id> \
--instance-type m5.large \
--key-name <key-name> \
--subnet-id <subnet-id> \
--security-group-ids <security-group-id> \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=roadrunner}]'When the instance reaches the Running state and its status checks pass, note its public IP address or DNS name. You can read it from the EC2 console or with aws ec2 describe-instances.
Connect to Your Instance
Connect over SSH with your key pair. On Ubuntu 24.04, the login user is ubuntu.
ssh -i /path/to/your-key.pem ubuntu@<public-ip>The login banner summarizes where the demo application is served, how to inspect the worker pool, and where the per-instance info note lives.
Open the Demo Application
nginx on port 80 makes RoadRunner reachable over the network. Browse to the instance's public address, or use curl from the instance, to see the demo PSR-7 status page.
A warm PHP worker renders the page. It reports the RoadRunner version, the PHP version, the PID of the worker serving the request, and how many requests that worker has served.
curl -s http://127.0.0.1/The status fields render as:
RoadRunner version = 2025.1.15 PHP version = 8.3.6 Serving worker PID = 11022 Requests served by this warm worker = 4 Host = 127.0.0.1 Server time = 2026-07-22T14:09:19+00:00To reach it from your browser, use http://<instance-public-ip>/.
Confirm the Services and the Security Posture
RoadRunner and nginx run under systemd and start automatically on boot. Confirm that both are active:
systemctl is-active roadrunner nginxactive activeRoadRunner binds its HTTP (8080), status (2114) and RPC (6001) listeners to loopback only. nginx is the sole public listener, on port 80. Confirm the binding:
sudo ss -tlnH | awk '{print $4}' | grep -E ':8080$|:2114$|:6001$|:80$' | sort0.0.0.0:80 127.0.0.1:2114 127.0.0.1:6001 127.0.0.1:8080 [::]:80The RoadRunner listeners are never exposed outside the instance. All external traffic arrives through nginx, which is ready for you to add your TLS certificate.
Inspect the Warm Worker Pool
The core of RoadRunner is its pool of persistent PHP worker processes. Inspect the pool with the rr workers command against the shipped config:
sudo rr workers -c /etc/roadrunner/.rr.yamlWorkers of [http]: +-------+--------+-------+--------+-------+----------------+ | PID | STATUS | EXECS | MEMORY | CPU % | CREATED | +-------+--------+-------+--------+-------+----------------+ | 11022 | ready | 2 | 31 MB | 0.08 | 51 seconds ago | | 11023 | ready | 2 | 31 MB | 0.08 | 51 seconds ago | | 11024 | ready | 2 | 31 MB | 0.08 | 51 seconds ago | | 11025 | ready | 1 | 31 MB | 0.08 | 51 seconds ago | +-------+--------+-------+--------+-------+----------------+Each worker is a warm PHP process that stays resident between requests. The EXECS column shows how many requests each worker has served. To see that the workers stay warm, make several requests to the JSON health route and watch the per-worker counter climb:
for i in 1 2 3 4; do curl -s http://127.0.0.1/healthz; echo; done{"status":"ok","worker_pid":11024,"php":"8.3.6","roadrunner":"2025.1.15","served_by_worker":4} {"status":"ok","worker_pid":11023,"php":"8.3.6","roadrunner":"2025.1.15","served_by_worker":4} {"status":"ok","worker_pid":11025,"php":"8.3.6","roadrunner":"2025.1.15","served_by_worker":4} {"status":"ok","worker_pid":11022,"php":"8.3.6","roadrunner":"2025.1.15","served_by_worker":5}The rising served_by_worker value confirms that the interpreter is bootstrapped once per worker and kept warm, not re-bootstrapped per request.
Health Checks
RoadRunner's built-in status plugin exposes a health endpoint on loopback for readiness probes. It returns HTTP 200 when the HTTP plugin's worker pool is healthy. Query it:
curl -s -o /dev/null -w 'rr status/health -> HTTP %{http_code}\n' 'http://127.0.0.1:2114/health?plugin=http'rr status/health -> HTTP 200The demo application also serves a JSON health route at /healthz through nginx, shown in Step 6. You can point a load balancer target group or an uptime monitor at it.
Replace the Demo Application with Your Own
The demo worker lives at /var/www/roadrunner-demo, with its Composer dependencies vendored under vendor/. To run your own application, either replace the demo worker script or point RoadRunner's server.command at your framework's RoadRunner worker.
The RoadRunner configuration is at /etc/roadrunner/.rr.yaml. The server.command line names the PHP worker that RoadRunner runs, and the http.pool.num_workers line controls the pool size:
sudo cat /etc/roadrunner/.rr.yamlReplace the worker script in place with your preferred editor. Run the editor as the roadrunner service account so file ownership stays correct:
sudo -u roadrunner nano /var/www/roadrunner-demo/worker.phpThen reload the worker pool without dropping the listener:
sudo rr reset -c /etc/roadrunner/.rr.yamlIf you change .rr.yaml itself, for example the pool size or the worker command, restart the service instead:
sudo systemctl restart roadrunnerPopular PHP frameworks, including Symfony and Laravel, ship first-class RoadRunner integration. Point server.command at the framework's RoadRunner entrypoint, run composer install for your project, and reload.
The Per-Instance Info Note
A root-only note at /root/roadrunner-info.txt records the resolved public URL and the key service hints for this instance. It contains no secret, because RoadRunner has none. Read it:
sudo cat /root/roadrunner-info.txtROADRUNNER_URL=http://<instance-public-ip>/ ROADRUNNER_HEALTH=http://127.0.0.1:2114/health?plugin=http ROADRUNNER_DEMO_APP=/var/www/roadrunner-demo ROADRUNNER_CONFIG=/etc/roadrunner/.rr.yaml NOTE=Loopback-bound rr behind nginx :80. Reload workers with: rr reset -c /etc/roadrunner/.rr.yamlTLS and Production Hardening
nginx is the single public entry point and is ready for your TLS certificate. For production, we recommend that you:
- Add a TLS certificate to the nginx virtual host, or terminate TLS at an Application Load Balancer in front of the instance and forward to port 80
- Restrict the security group so port 80 (or 443) is open only to the clients or load balancer that need it, and port 22 only to your management network
- Point an Application Load Balancer health check at
/healthz - Stream the nginx and RoadRunner journald logs to Amazon CloudWatch Logs for centralized monitoring
SecureAnyCloud support can help with connector configuration and AWS Certificate Manager integration.
Troubleshooting
- The demo page does not load over port 80. Confirm that the security group allows inbound port 80 from your client, and that both services are active with
systemctl is-active roadrunner nginx. Check nginx withsudo nginx -tandjournalctl -u nginx. - RoadRunner will not start. Inspect the journal with
journalctl -u roadrunner --no-pager | tail -50. A common cause is a syntax error in a replacedworker.php. Runphp -l /var/www/roadrunner-demo/worker.phpto lint it. - The worker pool looks empty. Run
sudo rr workers -c /etc/roadrunner/.rr.yaml. If no workers appear, restart the service withsudo systemctl restart roadrunnerand check again. - Changes to the worker are not reflected. Reload the pool with
sudo rr reset -c /etc/roadrunner/.rr.yaml. Workers cache the compiled application, so a reset is required after you edit the worker.
Support
SecureAnyCloud publishes and maintains this image with 24/7 support. For deployment help, TLS and load balancer configuration, worker-pool tuning, or onboarding your own PSR-7 application, contact the SecureAnyCloud support team at sales@secureanycloud.com.
RoadRunner is distributed under the MIT license.
Deploy on AWS
Launch RoadRunner on AWS with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com