Phusion Passenger on Ubuntu 24.04 on Azure User Guide
Overview
This guide covers the deployment and configuration of Phusion Passenger (open source edition) on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images. Passenger is a mature, polyglot application server that serves Ruby, Node.js and Python web applications through nginx. It spawns and supervises your application processes, load balances requests across workers, automatically restarts crashed workers, and supports zero downtime application restarts — with clear operational visibility through passenger-status and passenger-memory-stats.
The image installs Passenger integrated with nginx via the official Phusion APT repository (the libnginx-mod-http-passenger dynamic module), alongside distro Ruby 3.2 with Rack and Node.js 22 LTS with Express, so the polyglot runtime is real, not a claim. Passenger is an application server with no admin console or login — there is nothing to authenticate to — so this image ships two small demo apps pre-deployed and served through Passenger, proving the runtime is genuinely executing code on every request:
- a Ruby (Rack) app at
/that renders a live server timestamp and the worker PID - a Node.js (Express) app at
/node, running as a separate Passenger application group
Both apps render a live epoch and worker PID that change per request, so you can see Passenger executing real code rather than serving a static file.
What is included:
-
Phusion Passenger 6.1 (open source edition, MIT licensed) integrated with nginx via the official Phusion APT repo
-
Ruby 3.2 with the Rack gem, and Node.js 22 LTS with Express — a genuinely polyglot runtime
-
A Ruby (Rack) demo app at
/and a Node.js (Express) demo app at/node, each a distinct Passenger application group -
passenger-firstboot.servicesystemd oneshot that starts nginx, confirms the demo app answers, and writes a non-secret endpoints notes file -
Secure by default:
passenger_friendly_error_pages off(no stack traces to clients),server_tokens off, Passenger anonymous telemetry and security-update phone-home disabled -
nginx on
:80is the only public entry point; there is no admin login surface and no baked credential -
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
-
Active Azure subscription, SSH public key, VNet + subnet in target region
-
Subscription to the Phusion Passenger listing on Azure Marketplace
Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM) for development and light application workloads. Higher concurrency or CPU-bound apps should use Standard_D4s_v5 (4 vCPU, 16 GB RAM) or larger.
Step 1: Deploy from the Azure Portal
Search Phusion Passenger in Marketplace, select the cloudimg publisher, click Create. NSG rules: TCP 22 (admin) and TCP 80 (HTTP) from your client networks. Add TCP 443 if you terminate TLS on the instance.
Step 2: Deploy from the Azure CLI
RG="passenger-prod"; LOCATION="eastus"; VM_NAME="passenger-01"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/phusion-passenger-ubuntu-24-04/versions/<version>"
SSH_KEY="$(cat ~/.ssh/id_rsa.pub)"
az group create --name "$RG" --location "$LOCATION"
az network vnet create -g "$RG" --name passenger-vnet --address-prefix 10.100.0.0/16 --subnet-name passenger-subnet --subnet-prefix 10.100.1.0/24
az network nsg create -g "$RG" --name passenger-nsg
az network nsg rule create -g "$RG" --nsg-name passenger-nsg --name allow-ssh --priority 100 \
--source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 22 --access Allow --protocol Tcp
az network nsg rule create -g "$RG" --nsg-name passenger-nsg --name allow-http --priority 110 \
--destination-port-ranges 80 --access Allow --protocol Tcp
az vm create -g "$RG" --name "$VM_NAME" --image "$GALLERY_IMAGE_ID" \
--size Standard_B2s --storage-sku StandardSSD_LRS \
--admin-username azureuser --ssh-key-values "$SSH_KEY" \
--vnet-name passenger-vnet --subnet passenger-subnet --nsg passenger-nsg --public-ip-sku Standard
Step 3: Connect via SSH
ssh azureuser@<vm-ip>
nginx.service and passenger-firstboot.service start automatically on first boot — there is no admin account to set up, because Passenger has no login.
Step 4: Verify Passenger and nginx
passenger-config --version
sudo systemctl is-active nginx.service
sudo ss -tln | grep ':80 '
Expected output — Passenger is present and nginx is listening on :80:
Phusion Passenger(R) 6.1.7
active
LISTEN 0 511 0.0.0.0:80 0.0.0.0:*
LISTEN 0 511 [::]:80 [::]:*
You can also run Passenger's own install checker, which validates that the Passenger nginx module is correctly wired:
printf '\n\n\n\n' | sudo passenger-config validate-install
Expected output ends with Everything looks good. :-).

Step 5: Request the Ruby (Rack) Demo App
Passenger has no admin UI, so the value-proof for this image is the demo apps it serves. Request the Ruby app at / twice in a row:
curl -s http://127.0.0.1/
curl -s http://127.0.0.1/
Expected output — the epoch and Worker PID prove Passenger is executing the app on every request:
<!doctype html><html><head><title>Phusion Passenger - Ruby demo</title></head><body><h1>Phusion Passenger (open source)</h1><p>Ruby 3.2.3 application served by Passenger + nginx.</p><p>Server time: 2026-07-19T06:18:00Z epoch=1784441880</p><p>Worker PID: 9041</p></body></html>
The epoch value increments between calls — an in-process, per-request value that cannot come from a cached or static file. This is exactly the work an application server does that a plain static web server cannot.
Step 6: Request the Node.js (Express) Demo App
The same image also serves a Node.js Express app at /node, running as a separate Passenger application group — this is the polyglot story: one server, two languages, both managed by Passenger.
curl -s -o /dev/null -w 'HTTP %{http_code}\n' http://127.0.0.1/node
curl -s http://127.0.0.1/node
Expected output:
HTTP 200
<!doctype html><html><head><title>Phusion Passenger - Node demo</title></head><body><h1>Phusion Passenger (open source)</h1><p>Node.js v22.23.1 application served by Passenger + nginx.</p><p>Server time: 2026-07-19T06:18:00.789Z epoch=1784441880</p><p>Worker PID: 9215</p></body></html>
Note the Node.js worker has a different PID from the Ruby worker — Passenger runs them as independent application processes.

Step 7: Inspect Live Processes with passenger-status
passenger-status is Passenger's operational window into the application processes it manages. Warm both apps, then inspect:
curl -s -o /dev/null http://127.0.0.1/
curl -s -o /dev/null http://127.0.0.1/node
sudo passenger-status
Expected output — two application groups, each with a live worker process:
Version : 6.1.7
Instance: xxxxxxxx (nginx/1.24.0 (Ubuntu) Phusion_Passenger/6.1.7)
----------- General information -----------
Max pool size : 6
App groups : 2
Processes : 2
Requests in top-level queue : 0
----------- Application groups -----------
passenger-demo-node:
App root: /opt/passenger-demo/node
* PID: 9215 Sessions: 0 Processed: 2 Uptime: 2m 25s
passenger-demo-ruby:
App root: /opt/passenger-demo/ruby
* PID: 9041 Sessions: 0 Processed: 31 Uptime: 2m 32s
Passenger spawns these workers on demand and automatically restarts any that crash — kill a worker PID and the next request transparently spawns a replacement.

Step 8: Check Memory Use and Endpoint Notes
passenger-memory-stats reports the memory of the Passenger watchdog, core, and each application worker:
sudo passenger-memory-stats
sudo cat /var/lib/cloudimg/passenger-endpoints.notes
Expected output — the notes file documents the demo URLs and where to deploy your own app (Passenger has no credentials to rotate):
# Phusion Passenger — Endpoint Notes
# Generated on first boot by passenger-firstboot.service
#
PASSENGER_RUBY_DEMO_URL=http://<vm-ip>/
PASSENGER_NODE_DEMO_URL=http://<vm-ip>/node
PASSENGER_APP_ROOT=/opt/passenger-demo
INSPECT_PROCESSES=Run 'sudo passenger-status' to see live application process groups.

Step 9: Deploy Your Own App
Passenger serves any Ruby/Rack, Node.js, or Python app. Copy your app to the VM under /opt/passenger-demo/ (or a directory of your choice):
scp -r ./myapp azureuser@<vm-ip>:/tmp/myapp
ssh azureuser@<vm-ip> 'sudo mv /tmp/myapp /opt/myapp && sudo chown -R ppdemo:ppdemo /opt/myapp'
Then point nginx at it. For a Ruby/Rack app, set the server root to its public/ directory with passenger_enabled on;; for a Node.js app, add a location block with passenger_app_type node; and passenger_startup_file app.js;. The bundled /etc/nginx/sites-available/cloudimg-passenger vhost is a working reference for both patterns. After editing, validate and reload:
sudo nginx -t && sudo systemctl reload nginx
(Run these against your own app on your own VM — not executed as part of this guide's verification.)
Step 10: Server Components
| Component | Path |
|---|---|
| Passenger nginx module config | /etc/nginx/conf.d/mod-http-passenger.conf |
| nginx vhost | /etc/nginx/sites-available/cloudimg-passenger |
| Ruby (Rack) demo app | /opt/passenger-demo/ruby/config.ru |
| Node.js (Express) demo app | /opt/passenger-demo/node/app.js |
| Application user | ppdemo (unprivileged system user) |
| Logs | systemd journal (journalctl -u nginx.service) + /var/log/nginx/ |
| Firstboot script | /usr/local/sbin/passenger-firstboot.sh |
| Firstboot service | /etc/systemd/system/passenger-firstboot.service |
| Endpoint notes | /var/lib/cloudimg/passenger-endpoints.notes (mode 0644) |
| Firstboot sentinel | /var/lib/cloudimg/passenger-firstboot.done |
Step 11: Managing Passenger and nginx
sudo systemctl status nginx.service --no-pager
sudo systemctl reload nginx.service
sudo passenger-status
Passenger runs inside nginx, so managing the web server manages the application server. To pick up new application code without dropping requests, use a Passenger restart:
sudo passenger-config restart-app /opt/passenger-demo/ruby
Step 12: Security Recommendations
-
Keep friendly error pages off in production (already configured) so application stack traces are never shown to clients
-
Add TLS by fronting nginx with Let's Encrypt/Certbot and opening
:443 -
Restrict the NSG so
:80/:443are reachable only from your client networks if this is an internal service -
Run apps as an unprivileged user — the demo apps run as
ppdemo; keep your own apps off root -
Remove or replace the demo apps (
/opt/passenger-demo/) once you deploy your own application -
Patch the OS monthly with
sudo apt-get update && sudo apt-get upgrade && sudo reboot -
Do not enable Passenger Enterprise features on this image — it ships the open source edition only
Step 13: Support and Licensing
Phusion Passenger open source edition is licensed under the MIT License. There is no per-CPU or per-deployment fee.
cloudimg provides commercial support for this image separately from the upstream project.
- Email: support@cloudimg.co.uk
- Website: www.cloudimg.co.uk
- Support hours: 24/7 with guaranteed 24 hour response SLA
Deploy on Azure
Launch Phusion Passenger on Ubuntu 24.04 with 24/7 support from cloudimg.
View on Marketplace
Need Help?
Our support team is available 24/7.
support@cloudimg.co.uk