Node.js 22
Overview
Node.js 22 is the active LTS line ("Jod"), supported through April 2027, and remains the default JavaScript runtime for production server-side workloads. The SecureAnyCloud image installs Node.js 22.22.2 from the official NodeSource APT repository (deb.nodesource.com/node_22.x nodistro main). It is pinned with apt-mark hold, so an unattended apt-get upgrade cannot drift the major version.
Three companion tools come pre-installed at the global level: npm 10.9.7 (bundled with Node), pnpm 10.33.3 (a fast, disk-efficient alternative package manager) and pm2 7.0.1 (the de facto Node.js process manager).
A working Express 4.21 + EJS sample application lives at /opt/nodeapp/sac. It runs under a dedicated nodeapp system user, started by pm2-runtime through the sac-app.service systemd unit, and nginx fronts it as a reverse proxy. Per-VM secrets are generated at first boot.
What you get:
- Node.js 22.22.2 LTS (MIT) from NodeSource APT, supported through 2027-04-30
- npm 10.9.7 (bundled with Node), pnpm 10.33.3 and pm2 7.0.1, all installed globally
- Express 4.21 sample app at
/opt/nodeapp/sac, owned by thenodeappsystem user - Express with EJS templating, helmet (security headers), morgan (request logging) and ws (WebSocket)
- App bound to
127.0.0.1:3000, with nginx reverse-proxying port 80 to it - nginx serving
/static/directly from/opt/nodeapp/sac/public/ - Per-VM
SESSION_SECRET(32 hex) andADMIN_API_KEY(32 hex), rotated at first boot - Three systemd units:
nodejs-firstboot.service(oneshot),sac-app.service(pm2-runtime), and the standardnginx.service - 24/7 SecureAnyCloud support
Prerequisites
You need an active Azure subscription, an SSH key, and a VNet with a subnet.
Standard_B2s (4 GB RAM) is comfortable for a single-instance Express app under pm2.
Configure the NSG to allow inbound 22/tcp from your management CIDR and 80/tcp from any client CIDR that needs the Node.js app.
Step 1-3: Deploy the VM and Connect over SSH
Option A: Azure Portal
- Open the offer in the Azure Marketplace and select Create.
- Choose your subscription, resource group, VM name and region, and set Size to
Standard_B2s. - Under Administrator account, choose your username (
azureuserin the examples below) and select SSH public key. - Under Inbound port rules, allow SSH (22), then add HTTP (80) for the Node.js app.
- Select Review + create, then Create.
Option B: Azure CLI
az vm image terms accept --urn sac1647283583153:nodejs-22-ubuntu-24-04:default:latest
az vm create \
--resource-group myResourceGroup \
--name my-nodejs-22-vm \
--image sac1647283583153:nodejs-22-ubuntu-24-04:default:latest \
--size Standard_B2s \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-sku Standard
az vm open-port --resource-group myResourceGroup --name my-nodejs-22-vm --port 80 --priority 900Restrict the source address ranges on these rules to the networks that need them (Azure Portal > the VM > Networking).
Then connect over SSH, replacing <vm-ip> with the VM's public IP:
ssh azureuser@<vm-ip>Service Status + Versions
sudo systemctl is-active sac-app.service nginx.service nodejs-firstboot.service node --version npm --version pnpm --version pm2 --versionRead Per-VM Credentials
sudo cat /stage/scripts/nodejs-credentials.logThe credentials log holds the per-VM SESSION_SECRET, which Express uses to sign session cookies. It also holds the per-VM NODEAPP_ADMIN_API_KEY, which you pass in the X-Admin-Key header to reach /api/admin.
The same values are mirrored into /etc/nodeapp/sac.env, which the sac-app.service systemd unit reads at start-up.
HTTP Health + JSON API
curl -sf -o /dev/null -w 'homepage: HTTP %{http_code}\n' http://127.0.0.1/
curl -sf http://127.0.0.1/api/info | python3 -m json.tool | head -10The first call hits the Express homepage through nginx. The second confirms that the JSON API is alive and reports the same nodeVersion you saw in Step 4.
Browse the Sample App
Open http://<vm-ip>/ in a browser. The Express + EJS homepage renders the running Node version, the npm version, the request URL and the user agent. It also shows a small live block driven by the /ws WebSocket echo endpoint.
JSON API in Browser
Visit http://<vm-ip>/api/info. Chrome and Firefox pretty-print the JSON. Your front-end SPA, mobile client or a downstream service would consume the same response programmatically.
Admin API with Key
/api/admin requires the X-Admin-Key header. Visiting it without the key in a plain browser returns HTTP 401 Unauthorized with a JSON error body. This response proves that the key check is wired correctly.
To call it successfully from your terminal, run:
KEY=$(sudo grep '^NODEAPP_ADMIN_API_KEY=' /stage/scripts/nodejs-credentials.log | cut -d= -f2-)
curl -sf -H "X-Admin-Key: ${KEY}" http://127.0.0.1/api/admin | python3 -m json.toolpm2 Process Status
sac-app.service runs the app through pm2-runtime, so the systemd unit is the supervisor. To list the running pm2 processes as the nodeapp user, run:
sudo -u nodeapp pm2 list
sudo journalctl -u sac-app.service --no-pager -n 20The pm2 list output shows a single sac-app process with its CPU, memory and restart counters. The journal tail shows the last twenty Express request log lines from morgan.
Install Additional npm Packages
To add a runtime dependency to the SecureAnyCloud sample app, run the following and replace <package> with the npm package you need:
cd /opt/nodeapp/sac
sudo -u nodeapp npm install <package>
sudo systemctl restart sac-app.serviceRun the install as the nodeapp user, so that node_modules/ keeps the right ownership. The systemd restart makes pm2-runtime re-spawn the worker against the updated module tree.
Tune pm2 Workers
The SecureAnyCloud sample app ships in fork mode (one worker). To run cluster mode across all CPU cores, edit /opt/nodeapp/sac/ecosystem.config.js and set the instances field on the sac-app block:
instances: "max", exec_mode: "cluster",Then restart:
sudo systemctl restart sac-app.service
sudo -u nodeapp pm2 listinstances: "max" tells pm2 to spawn one worker per CPU core (2 on Standard_B2s, 4 on Standard_B4s_v2, and so on). Use a number such as instances: 2 if you want to leave headroom for nginx and the OS.
Add Your Domain
Edit /etc/nginx/sites-available/nodeapp-sac, set the server_name to your hostname, and reload nginx:
sudo sed -i 's/server_name _;/server_name apps.example.com;/' /etc/nginx/sites-available/nodeapp-sac
sudo nginx -t sudo systemctl reload nginxnginx -t checks the configuration syntax without applying it. reload swaps configs without dropping live connections.
Install Let's Encrypt SSL
Point your domain's DNS A record at this VM, then run:
sudo apt-get install -y python3-certbot-nginx
sudo certbot --nginx -d apps.example.com -m you@example.com --agree-tos --non-interactive --redirectCertbot edits /etc/nginx/sites-available/nodeapp-sac to add the SSL server block plus a :80 → :443 redirect, then reloads nginx.
The certbot.timer systemd unit handles renewal and runs twice daily.
Update Node.js or Switch to nvm
Node.js 22 is held at the major version with apt-mark hold nodejs, so an unattended apt-get upgrade never crosses a major boundary. To take a 22.x patch upgrade explicitly, run:
sudo apt-mark unhold nodejs
sudo apt-get update
sudo apt-get install -y --only-upgrade nodejs
sudo apt-mark hold nodejs
sudo systemctl restart sac-app.serviceTo run multiple Node versions side by side (for example a legacy Node 18 service and a new Node 22 service on the same VM), install nvm per user and let each developer pin their own runtime:
curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash . ~/.bashrc nvm install 22 nvm use 22The system-wide NodeSource Node 22 stays in place for sac-app.service.
Logs and Troubleshooting
sudo journalctl -u sac-app.service --no-pager -n 80
sudo journalctl -u nginx.service --no-pager -n 30
sudo tail -50 /var/log/nginx/access.log
sudo tail -50 /var/log/nginx/error.log
sudo -u nodeapp pm2 logs sac-app --lines 50 --nostreamThe Express app logs all requests through morgan to stdout, so journalctl -u sac-app.service is the canonical event log.
nginx has its own access and error logs at /var/log/nginx/. pm2 also keeps per-process logs under /var/lib/nodeapp/.pm2/logs/ (visible through pm2 logs).
Security
- Per-VM
SESSION_SECRET(32 hex chars), which Express uses to sign session cookies. - Per-VM
ADMIN_API_KEY(32 hex chars), required in theX-Admin-Keyheader for/api/admin. NODE_ENV=production, so Express disables verbose error pages and enables view caching.- The
helmetmiddleware is enabled. It setsX-Frame-Options,X-Content-Type-Options,Strict-Transport-Security, CSP and more. - The app binds to 127.0.0.1:3000 only, so it is never directly reachable from the public network.
- nginx reverse-proxies port 80 to
127.0.0.1:3000, giving a single ingress point that is easy to put behind a WAF or CDN. - NSG inbound is restricted to
22/tcp(management) and80/tcp(clients) by default. python3-certbot-nginxis available for free Let's Encrypt TLS (Step 14).nodeappis a dedicated system user with no login shell, so the app cannot escalate to root.
Support
SecureAnyCloud provides 24/7/365 expert technical support. Guaranteed response within 24 hours, one hour average for critical issues. Contact sales@secureanycloud.com.
Deploy on Azure
Launch Node.js 22.22.2 LTS 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