Phusion Passenger Application Server on AWS User Guide
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.servicesystemd 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
: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; 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. :-)

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.

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.

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.

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/: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/rubyand/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