Application Servers AWS

Phusion Passenger Application Server on AWS User Guide

| Product: Phusion Passenger Application Server on AWS

Overview

This guide covers the deployment and operation of Phusion Passenger (open source edition) on AWS using cloudimg Marketplace AMIs. 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 repository

  • 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

  • A dedicated EBS application data volume mounted at /opt/passenger-demo, so your app code lives on its own independently-resizable disk

  • passenger-firstboot.service systemd oneshot that starts nginx, confirms the demo app answers, resolves the instance public IP, 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 :80 is 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; the AWS SSM agent installed for Systems Manager integration

  • 24/7 cloudimg support with guaranteed 24 hour response SLA

Prerequisites

  • An AWS account subscribed to the Phusion Passenger listing on AWS Marketplace

  • An EC2 key pair, a VPC with a public subnet, and a security group allowing inbound TCP 22 (SSH) and TCP 80 (HTTP) from your client networks

Recommended instance type: m5.large (2 vCPU, 8 GB RAM) for development and light-to-moderate application workloads. Higher concurrency or CPU-bound apps should use m5.xlarge or larger.

Connecting to your instance

Each OS variant of this listing has its own default SSH login user. Use the row matching the AMI variant you launched.

OS variant SSH login user Example
Ubuntu 24.04 LTS ubuntu ssh -i your-key.pem ubuntu@<instance-public-ip>

Step 1: Launch from the AWS Console

In the AWS Marketplace or the EC2 console, subscribe to the Phusion Passenger listing and choose Launch. Select an m5.large (or larger) instance type, your key pair, and a security group that allows:

  • TCP 22 (SSH) from your management network
  • TCP 80 (HTTP) from your client networks

Add TCP 443 if you plan to terminate TLS on the instance.

Step 2: Launch from the AWS CLI

AMI_ID=<the-phusion-passenger-ami-id>
aws ec2 run-instances \
  --image-id "$AMI_ID" \
  --instance-type m5.large \
  --key-name your-key \
  --security-group-ids sg-xxxxxxxx \
  --subnet-id subnet-xxxxxxxx \
  --associate-public-ip-address \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=passenger-01}]'

Step 3: Connect via SSH

ssh -i your-key.pem ubuntu@<public-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.8
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. :-):

 * Checking whether this Passenger install is in PATH... ✓
 * Checking whether there are no other Passenger installations... ✓

Everything looks good. :-)

passenger-config reports Phusion Passenger 6.1.8, nginx.service is active, passenger-config validate-install reports Everything looks good, and nginx is listening on port 80

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-22T01:48:45Z epoch=1784684925</p><p>Worker PID: 6902</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. You can also open http://<instance-public-ip>/ in a browser.

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-22T01:48:45.635Z epoch=1784684925</p><p>Worker PID: 6920</p></body></html>

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

curl of the Ruby app at / and the Node.js app at /node, both returning HTTP 200 with a live server timestamp, epoch and worker PID, proving genuine per-request execution in two languages

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.8
Instance: xxxxxxxx (nginx/1.24.0 (Ubuntu) Phusion_Passenger/6.1.8)

----------- 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: 6920    Sessions: 0       Processed: 3       Uptime: 0s
passenger-demo-ruby:
  App root: /opt/passenger-demo/ruby
  * PID: 6902    Sessions: 0       Processed: 2       Uptime: 0s

Passenger spawns these workers on demand and automatically restarts any that crash. Kill a worker PID and the next request transparently spawns a replacement.

passenger-status showing two application groups, passenger-demo-node and passenger-demo-ruby, each with a live worker process, its PID, sessions, requests processed and uptime

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):

----- Passenger processes -----
PID   VMSize     Private  Name
-------------------------------
6833  359.8 MB   3.3 MB   Passenger watchdog
6837  1035.5 MB  3.7 MB   Passenger core
6902  228.8 MB   5.3 MB   Passenger RubyApp: passenger-demo-ruby
6920  1031.9 MB  16.2 MB  Passenger NodeApp: /opt/passenger-demo/node

# Phusion Passenger — Endpoint Notes
PASSENGER_RUBY_DEMO_URL=http://<instance-public-ip>/
PASSENGER_NODE_DEMO_URL=http://<instance-public-ip>/node
PASSENGER_APP_ROOT=/opt/passenger-demo
INSPECT_PROCESSES=Run 'sudo passenger-status' to see live application process groups.

passenger-memory-stats listing the Passenger watchdog, core, and the Ruby and Node application workers with their memory use, alongside the endpoints notes file documenting the demo URLs

Step 9: Deploy Your Own App

Passenger serves any Ruby/Rack, Node.js, or Python app. Your application tier lives on the dedicated EBS volume mounted at /opt/passenger-demo. Copy your app there:

scp -r -i your-key.pem ./myapp ubuntu@<public-ip>:/tmp/myapp
ssh -i your-key.pem ubuntu@<public-ip> 'sudo mv /tmp/myapp /opt/passenger-demo/myapp && sudo chown -R ppdemo:ppdemo /opt/passenger-demo/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 instance, 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 data volume /opt/passenger-demo (dedicated EBS volume)
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, warm the app group and use a Passenger restart:

curl -s -o /dev/null http://127.0.0.1/
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, or terminate TLS at an Application Load Balancer with AWS Certificate Manager

  • Restrict the security group so :80/:443 are 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/ruby and /opt/passenger-demo/node) 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 AWS

Launch Phusion Passenger with 24/7 support from cloudimg.

View on Marketplace

Need Help?

Our support team is available 24/7.

support@cloudimg.co.uk