Application Servers Azure

RoadRunner on Ubuntu 24.04 on Azure User Guide

| Product: RoadRunner on Ubuntu 24.04 LTS on Azure

Overview

This guide covers the deployment and configuration of RoadRunner on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images. 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, RoadRunner starts a pool of long-lived PHP worker processes once and keeps them warm, then dispatches each incoming request to a worker over an efficient pipes relay. The result is dramatically 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.

The image ships the official prebuilt RoadRunner binary (version 2025.1.15, MIT), PHP 8.3 and Composer, plus a minimal demo PSR-7 worker application that is already vendored and running so the whole stack is proven the moment the instance boots. RoadRunner binds only to loopback and is exposed to the network exclusively through an nginx reverse proxy on port 80 that is ready for your TLS certificate.

What is included:

  • RoadRunner 2025.1.15 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 (dependencies spiral/roadrunner-http + nyholm/psr7 already vendored)

  • Configuration at /etc/roadrunner/.rr.yaml with the HTTP, status and RPC listeners all bound to 127.0.0.1

  • roadrunner.service systemd unit auto-starting on boot, running as the roadrunner system user

  • nginx.service reverse proxy on TCP 80 forwarding to the RoadRunner HTTP listener on 127.0.0.1:8080

  • roadrunner-firstboot.service systemd oneshot that 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/health), 127.0.0.1:6001 (RPC control channel)

  • Ubuntu 24.04 LTS base with latest security patches applied at build time

  • Azure Linux Agent for seamless cloud integration and SSH key injection

  • 24/7 cloudimg support with 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

Step 1: Deploy from the Azure Portal

  1. Sign in to the Azure Portal.

  2. Search the Marketplace for RoadRunner on Ubuntu 24.04 LTS and select the cloudimg listing.

  3. Choose Create, then select or create a resource group and region.

  4. Pick a VM size. Standard_B2s (2 vCPU, 4 GiB) is a comfortable starting point for the demo application; scale the size and the num_workers setting up together for production workloads.

  5. Provide an administrative username (azureuser) and your SSH public key.

  6. 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.

  7. Review and create. RoadRunner and nginx start automatically once the instance boots.

Step 2: Deploy from the Azure CLI

You can deploy the same image from the command line. Replace the placeholders with your own values, then open the HTTP port to the clients that need it.

az vm create \
  --resource-group my-roadrunner-rg \
  --name my-roadrunner-vm \
  --image <cloudimg-roadrunner-image-urn> \
  --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 900

Step 3: Connect via SSH

Connect to the instance as azureuser with your private key:

ssh azureuser@<vm-ip>

Step 4: Verify the RoadRunner service

RoadRunner and nginx are enabled and start on boot. Confirm both units are active:

sudo systemctl start roadrunner nginx
sudo systemctl is-active roadrunner nginx

Both should report active. The image also writes a per-VM service-info note you can read at any time:

sudo cat /root/roadrunner-info.txt

roadrunner.service and nginx.service both active, with every RoadRunner listener bound to loopback and only nginx facing the network on port 80

Step 5: Confirm 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'

You will see 127.0.0.1:8080 (RoadRunner HTTP), 127.0.0.1:2114 (status/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.

Step 6: Check the RoadRunner version and health endpoint

Confirm the installed RoadRunner version and 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 and the health endpoint returns HTTP 200 when the HTTP plugin has ready workers. Use this loopback endpoint for readiness and liveness checks.

rr version 2025.1.15 reported by the Go binary, the status health endpoint returning HTTP 200 on loopback, and the live PHP worker pool listed by rr workers

Step 7: 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.yaml

You will see 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 is what keeps per-request overhead low.

Step 8: 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 it answers through nginx and observe the same warm worker serving repeated requests:

curl -s -o /dev/null -w 'GET / -> HTTP %{http_code}\n' http://localhost/
curl -s http://localhost/healthz

The /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 — proof that the interpreter is kept warm between requests rather than restarted:

for i in 1 2 3 4 5; do curl -s http://localhost/healthz; echo; done

The demo app answering GET / with HTTP 200 through nginx, a health JSON payload from the warm PHP worker showing PHP 8.3 and RoadRunner 2025.1.15, and repeated calls served by persistent workers with a climbing request counter

Step 9: Review 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.yaml

Key settings are server.command (the PHP worker entry point), http.address (the loopback HTTP listener nginx proxies to), http.pool.num_workers (how many warm workers to keep), status.address (the loopback health endpoint) and rpc.listen (the control channel used by rr workers and rr reset).

The /etc/roadrunner/.rr.yaml configuration showing the server command, the loopback HTTP, status and RPC addresses, the worker pool size, and the commands to swap in your own PSR-7 application

Step 10: 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/framework

Then 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.yaml

Step 11: Enable 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> and configure the nginx server block to terminate HTTPS while continuing 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 handles TLS termination.

Step 12: Managing the service

RoadRunner is managed by systemd. Check the service status:

sudo systemctl status roadrunner --no-pager --lines=0

To reload after a configuration or application change without dropping connections, prefer rr reset over a full restart — it recycles the worker pool gracefully while the server keeps accepting requests (as used in Step 10). For a full stop, start or restart of the service, and to follow the structured JSON logs when troubleshooting, use the standard systemd commands:

sudo systemctl restart roadrunner
sudo journalctl -u roadrunner -f

Step 13: Troubleshooting

  • 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 will show here.

  • 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.

  • Health endpoint returns 500 — the HTTP plugin has no ready workers. Inspect the journal and confirm 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 sudo rr reset -c /etc/roadrunner/.rr.yaml.

Step 14: 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/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 roadrunner system user (the default) rather than root.

Step 15: Support and Licensing

RoadRunner is distributed under the MIT license. This cloudimg image is a repackaged open source product with additional charges for cloudimg 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. cloudimg maintains the image with security patches and provides a paired deployment guide for every release.

Need Help?

cloudimg provides 24/7 support with a guaranteed 24 hour response SLA. Contact support through the details on your Azure Marketplace listing.