Node.js
Overview
This image runs Node.js 22 LTS, the JavaScript runtime that powers modern API servers, real-time services, server-side rendered web apps and JavaScript build tooling. A small sample Express + EJS application runs behind nginx, so you can verify the stack in minutes and then replace the sample with your own code.
What you get:
- Node.js installed from the official NodeSource repository (Node 22.x) with the bundled npm, plus pnpm and PM2 installed globally.
- A sample Express + EJS application under
/opt/nodeapp/sac, owned by the unprivilegednodeappsystem account and run by PM2 under systemd assac-app.service. - nginx 1.24 in front of the runtime on port 80. It reverse-proxies dynamic routes to the Express app on
127.0.0.1:3000and serves static assets directly from/opt/nodeapp/sac/public/. - Two per-instance secrets generated on the first boot of every deployed instance: a 32-byte hex
SESSION_SECRETused for Express session signing, and a 32-byte hexADMIN_API_KEYthat protects the sample admin endpoint. Two instances launched from the same Amazon Machine Image never share these values. - The secrets are written to
/root/nodejs-credentials.txtwith mode 0600, so only the root user can read them, and to the application's environment file at/etc/nodeapp/sac.env. - No instance metadata reads in the sample app. The Express homepage and the JSON APIs serve on whatever public DNS name or IP address visitors reach the instance on, so deployments behind an Application Load Balancer, in a private subnet, or on a custom domain all work without changes.
Prerequisites
Before you deploy this image, you need:
- An Amazon Web Services account where you can launch EC2 instances.
- IAM permissions to launch instances, create security groups and subscribe to AWS Marketplace products.
- An EC2 key pair in the target Region for SSH access to the instance.
- A VPC and subnet in the target Region, with a security group that allows inbound port 22 from your management network and inbound port 80 from the networks your visitors use.
- The AWS CLI (version 2) installed locally, if you plan to deploy from the command line.
Launch the Instance from the AWS Marketplace
Sign in to the AWS Management Console, open the EC2 service, and select Launch instance. Under Application and OS Images, choose AWS Marketplace AMIs and search for Node.js LTS. Select the SecureAnyCloud listing and choose Select, then Continue on the subscription summary.
Pick an instance type of m5.large or larger. Choose your EC2 key pair under Key pair (login). Under Network settings, select your VPC and subnet, and either create or select a security group that allows inbound port 22 from your management network and inbound port 80 from the networks your visitors use. Leave the root volume at the default size or larger.
Select Launch instance. First-boot initialization takes approximately one minute after the instance state becomes Running and the status checks pass.
Launch the Instance from the AWS CLI
The following command launches an instance from the SecureAnyCloud Node.js LTS Marketplace AMI into an existing subnet and security group. Replace <ami-id> with the AMI ID shown on the Marketplace listing, <key-name> with your EC2 key pair name, <subnet-id> with your subnet ID, and <security-group-id> with a security group that opens ports 22 and 80 as described above.
aws ec2 run-instances \
--image-id <ami-id> \
--instance-type m5.large \
--key-name <key-name> \
--subnet-id <subnet-id> \
--security-group-ids <security-group-id> \
--block-device-mappings '[{"DeviceName":"/dev/sda1","Ebs":{"VolumeSize":30,"VolumeType":"gp3"}}]' \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=nodejs-sac-01}]'The instance reaches the running state in roughly thirty seconds. First-boot credential rotation runs as a oneshot systemd service and takes another twenty to forty seconds before sac-app.service and nginx.service are fully up.
Connecting to your instance
The SSH login user for the Node.js 22 LTS on Ubuntu 24.04 AMI is ubuntu. From your workstation, run:
ssh -i <path/to/key.pem> ubuntu@<instance-public-dns>Retrieve your per-instance credentials
Read the credentials file as root:
sudo cat /root/nodejs-credentials.txtExample output:
# Node.js 22 LTS — Per-instance Credentials
# Generated: Sat May 24 21:28:25 UTC 2026 NODEAPP_URL=http://32.192.52.220/ NODEAPP_API_INFO_URL=http://32.192.52.220/api/info NODEAPP_ADMIN_URL=http://32.192.52.220/api/admin NODEAPP_ADMIN_API_KEY=<set-at-first-boot> NODEAPP_SESSION_SECRET=<32-byte hex, redacted in this guide>NODEAPP_ADMIN_API_KEY is the secret that the sample admin endpoint expects in the X-Admin-Key HTTP header. NODEAPP_SESSION_SECRET is the per-instance secret that Express session middleware uses to sign session cookies. Both values are unique to this instance.
Open the sample welcome page
In a browser, go to http://<instance-public-dns>/. The SecureAnyCloud sample Express app returns a rendered EJS page with live Node.js runtime metadata and links to the available endpoints:
Exercise the JSON API
The /api/info endpoint returns runtime metadata as JSON. It needs no authentication and is safe to call from a load balancer health check.
curl -s http://<instance-public-dns>/api/infoExample response:
The static health check file is served directly by nginx without invoking Node:
curl -s http://<instance-public-dns>/static/healthcheck.txtIt returns the literal string ok.
Call the authenticated admin endpoint
The /api/admin endpoint returns Node runtime stats only when you supply the per-instance ADMIN_API_KEY as the X-Admin-Key HTTP header. Calls without the header receive HTTP 401.
KEY=$(sudo grep '^NODEAPP_ADMIN_API_KEY=' /root/nodejs-credentials.txt | cut -d= -f2-)
curl -s -H "X-Admin-Key: ${KEY}" http://127.0.0.1/api/adminExample authenticated response:
Inspect the running stack
The runtime consists of three systemd units: nodejs-firstboot.service (oneshot, runs once at first boot), sac-app.service (the Express app under PM2's pm2-runtime), and nginx.service (the reverse proxy). Inspect them with:
sudo systemctl is-active nodejs-firstboot.service sac-app.service nginx.service
sudo systemctl status sac-app.service --no-pager sudo PM2_HOME=/var/lib/nodeapp/.pm2 pm2 listThe Express process logs to the journal:
sudo journalctl -u sac-app.service -fPM2 writes its own stdout and stderr to /var/log/nodeapp/out.log and /var/log/nodeapp/error.log.
Replace the sample app with your own code
The sample lives under /opt/nodeapp/sac and is owned by nodeapp:nodeapp. To deploy your own application, first stop the service and copy your code into place:
bash sudo systemctl stop sac-app.service sudo rsync -av --delete <your-app>/ /opt/nodeapp/sac/ sudo chown -R nodeapp:nodeapp /opt/nodeapp/sac
Then install dependencies as the unprivileged user:
bash sudo -u nodeapp -H bash -c "cd /opt/nodeapp/sac && npm install --omit=dev"
Either update ecosystem.config.js to point at your entry script, or replace it entirely. The default points at ./server.js listening on 127.0.0.1:3000, because that is where nginx forwards traffic. Keep that port unless you also update /etc/nginx/sites-available/nodeapp-sac.
Start the service again:
bash sudo systemctl start sac-app.service sudo systemctl status sac-app.service --no-pager
If your app needs additional per-instance environment variables, add them to /etc/nodeapp/sac.env (mode 0640, owner root:nodeapp). sac-app.service reads this file through EnvironmentFile=, so the variables are exported into the Node process at start.
Tune the V8 heap
Node.js 22's default V8 heap is around 1.7 GB. For memory-heavy SSR workloads, raise it explicitly. Edit /etc/nodeapp/sac.env and add:
NODE_OPTIONS=--max-old-space-size=4096Then run sudo systemctl restart sac-app.service to apply the change.
Enable HTTPS
The image ships with nginx serving HTTP only, on port 80. To add HTTPS, choose one of two options.
Recommended for production: terminate TLS at an upstream Application Load Balancer or CloudFront distribution and forward HTTP to the EC2 instance. This is the cleanest option for autoscaling groups and lets the load balancer rotate certificates centrally.
Per-instance Let's Encrypt: if you run a single instance with a public DNS name, install certbot and let it modify the nginx config in place:
bash sudo apt-get install -y certbot python3-certbot-nginx sudo certbot --nginx -d <your-domain>
certbot adds a listen 443 ssl block to /etc/nginx/sites-available/nodeapp-sac and schedules a renewal timer.
WebSocket smoke test
The sample app also runs a WebSocket echo service at /ws. From an instance with wscat installed, run:
npx wscat -c ws://<instance-public-dns>/ws < {"type":"hello","nodeVersion":"v22.22.2"} > hello from sac < {"type":"echo","payload":"hello from sac"}nginx's site config already sets proxy_http_version 1.1 plus the Upgrade and Connection headers under location /ws, so the upgrade succeeds without further changes.
Logs
Each component writes its logs to a different place:
- First-boot script: journalctl -u nodejs-firstboot.service and /var/log/sac/sac-firstboot.log
- Express app via PM2: journalctl -u sac-app.service, /var/log/nodeapp/out.log, /var/log/nodeapp/error.log
- nginx access: /var/log/nginx/access.log
- nginx errors: /var/log/nginx/error.log
Updating Node.js and the app
To apply updates from the NodeSource repository on a running instance:
sudo apt-get update
sudo apt-get -y install nodejs
sudo systemctl restart sac-app.serviceUpdate PM2 itself with npm:
sudo npm install -g pm2@latest
sudo systemctl restart sac-app.serviceResetting the per-instance secrets
To rotate ADMIN_API_KEY or SESSION_SECRET on an already-running instance, remove the first-boot sentinel, restart the first-boot service, and restart the app:
sudo rm -f /var/lib/sac/data/nodejs-firstboot.done
sudo systemctl start nodejs-firstboot.service
sudo systemctl restart sac-app.service
sudo cat /root/nodejs-credentials.txtA fresh ADMIN_API_KEY and SESSION_SECRET are generated, and the credentials file is rewritten.
Support
SecureAnyCloud provides 24/7 technical support for this image. Open a support request through the SecureAnyCloud support portal for help with Node.js deployment, PM2 cluster mode, nginx tuning, TLS through Let's Encrypt or an upstream load balancer, log shipping, runtime upgrades, or extending the sample app for production.
Screenshots
The SecureAnyCloud sample Express + EJS welcome page served by nginx on port 80, with live Node.js runtime metadata.
Runtime metadata returned by the unauthenticated /api/info JSON endpoint, including the Node version and dependency versions.
The per-instance admin endpoint returning runtime stats when the X-Admin-Key header is supplied.
Deploy on AWS
Launch Node.js LTS on AWS with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com