Deployment guide

Phusion Passenger

Overview

Phusion Passenger (open source edition) 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. passenger-status and passenger-memory-stats give you clear operational visibility.

This SecureAnyCloud image runs Passenger integrated with nginx on Ubuntu 24.04 on Azure. Passenger is an application server with no admin console or login, so there is nothing to authenticate to. Instead, the image ships two small demo apps, pre-deployed and served through Passenger, to prove that the runtime executes 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 you get:

  • Phusion Passenger 6.1 (open source edition, MIT licensed), integrated with nginx through the official Phusion APT repository (the libnginx-mod-http-passenger dynamic module)
  • Ruby 3.2 (from the distribution) with the Rack gem, and Node.js 22 LTS with Express, so the polyglot runtime is real
  • A Ruby (Rack) demo app at / and a Node.js (Express) demo app at /node, each a distinct Passenger application group
  • A passenger-firstboot.service systemd oneshot that starts nginx, confirms that the demo app answers, and writes a non-secret endpoints notes file
  • Secure defaults: passenger_friendly_error_pages off (no stack traces to clients), server_tokens off, and Passenger anonymous telemetry and security-update phone-home disabled
  • nginx on :80 as the only public entry point, with no admin login surface and no baked credential
  • An Ubuntu 24.04 LTS base with the latest security patches applied at build time
  • Azure Linux Agent for cloud integration and SSH key injection
  • 24/7 SecureAnyCloud support with a 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.

1

Deploy from the Azure Portal

Search for Phusion Passenger in Azure Marketplace, select the SecureAnyCloud publisher, and click Create. Allow TCP 22 (admin) and TCP 80 (HTTP) from your client networks in the network security group (NSG). Add TCP 443 if you terminate TLS on the instance.

2

Deploy from the Azure CLI

Run the following commands to create the resource group, network, NSG rules and VM:

RG="passenger-prod"; LOCATION="eastus"; VM_NAME="passenger-01" IMAGE_URN="sac1647283583153:phusion-passenger-ubuntu-24-04:default:latest" 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
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:phusion-passenger-ubuntu-24-04:default:latest
az vm create -g "$RG" --name "$VM_NAME" --image "$IMAGE_URN" \
  --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
3

Connect via SSH

Connect to the VM over 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.

4

Verify Passenger and nginx

Check the Passenger version, the nginx service state and the listening port:

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. It validates that the Passenger nginx module is wired up correctly:

printf '\n\n\n\n' | sudo passenger-config validate-install

The expected output ends with Everything looks good. :-).

5

Request the Ruby (Rack) Demo App

Passenger has no admin UI, so the demo apps are how you confirm it works. 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 values prove that Passenger executes 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 increases between calls. It is an in-process, per-request value that cannot come from a cached or static file. This is the work an application server does that a plain static web server cannot.

6

Request the Node.js (Express) Demo App

The same image also serves a Node.js Express app at /node. It runs as a separate Passenger application group, so one server runs two languages and Passenger manages both. Request it:

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>

The Node.js worker has a different PID from the Ruby worker, because Passenger runs them as independent application processes.

7

Inspect Live Processes with passenger-status

passenger-status shows the application processes that Passenger manages. Warm both apps, then inspect them:

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. If you kill a worker PID, the next request transparently spawns a replacement.

8

Check Memory Use and Endpoint Notes

passenger-memory-stats reports the memory used by the Passenger watchdog, the Passenger core and each application worker. Run it, then read the endpoint notes file:

sudo passenger-memory-stats
sudo cat /var/lib/sac/data/passenger-endpoints.notes

Expected output: the notes file lists 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.
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 your app. For a Ruby/Rack app, set the server root to its public/ directory and add 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/sac-passenger vhost is a working reference for both patterns. After editing, validate the configuration and reload nginx:

sudo nginx -t && sudo systemctl reload nginx

Run these commands against your own app on your own VM.

10

Server Components

The image places these components at the following paths:

  • Passenger nginx module config: /etc/nginx/conf.d/mod-http-passenger.conf
  • nginx vhost: /etc/nginx/sites-available/sac-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) and /var/log/nginx/
  • Firstboot script: /usr/local/sbin/passenger-firstboot.sh
  • Firstboot service: /etc/systemd/system/passenger-firstboot.service
  • Endpoint notes: /var/lib/sac/data/passenger-endpoints.notes (mode 0644)
  • Firstboot sentinel: /var/lib/sac/data/passenger-firstboot.done
11

Managing Passenger and nginx

Passenger runs inside nginx, so managing the web server also manages the application server. Check the service, reload it, and inspect the processes:

sudo systemctl status nginx.service --no-pager sudo systemctl reload nginx.service
sudo passenger-status

To pick up new application code without dropping requests, use a Passenger restart:

sudo passenger-config restart-app /opt/passenger-demo/ruby
12

Security Recommendations

  • Keep friendly error pages off. They are already off in this image, so application stack traces are never shown to clients.
  • Add TLS by fronting nginx with Let's Encrypt and Certbot, and open :443.
  • Restrict the NSG so :80 and :443 are reachable only from your client networks if this is a private 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. This image ships the open source edition only.
13

Support and Licensing

Phusion Passenger open source edition is licensed under the MIT License. There is no per-CPU or per-deployment fee.

SecureAnyCloud provides commercial support for this image, separately from the upstream project.

  • Email: sales@secureanycloud.com
  • Website: secureanycloud.com
  • Support hours: 24/7 with a guaranteed 24 hour response SLA

Deploy on Azure

Launch Phusion Passenger on Ubuntu 24.04 with 24/7 support from SecureAnyCloud.

View on Marketplace

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure

Launch Phusion Passenger 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

Deploy on Azure