Installing Node.js on cloud hosting takes about ten minutes: install a version manager, pick the current LTS release, run your app under a process supervisor, and put Nginx in front of it as a reverse proxy. The part people skip is the supervisor, and that is why so many first deployments die the moment the SSH session closes. This guide covers the full path on a plain Ubuntu server plus what changes on managed Node platforms.
nvm install --lts to get Node 24 (the current Active LTS), then keep the app alive with a systemd unit or PM2 and proxy port 80 and 443 to it through Nginx. Open only 22, 80 and 443 in the firewall and bind the app to 127.0.0.1. On managed platforms like Render, Railway or App Platform you skip all of that and just set a build command and a start command.Below: choosing the right Node version, three ways to install it, a working systemd unit, the PM2 alternative, an Nginx server block that handles WebSockets, firewall rules, and the differences that decide whether you want a raw VPS at all.
Pick the right Node.js version first
Node releases follow a predictable schedule: even numbered majors become LTS, odd numbered ones are current only and should not go near production. As of September 2026 the picture from the official release page looks like this.
| Version | Codename | Status | Use it? |
|---|---|---|---|
| Node 24 | Krypton | Active LTS | Yes, the default choice for new deployments |
| Node 22 | Jod | Maintenance LTS | Fine if you are already on it, plan the move |
| Node 20 | Iron | End of life | No, it no longer receives security fixes |
| Odd majors | Current line | Short lived | Development and testing only |
The official guidance is blunt: production applications should only run an Active LTS or Maintenance LTS release. Pin the version in your repository with an .nvmrc file so the server, your laptop and CI all agree.
Three ways to install Node.js on a server
Use nvm when you want per project versions and painless upgrades. Use the NodeSource repository when you want the system package manager to own it. Use the official binary tarball when you want no package manager at all.
The nvm route, which is what most teams end up on:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash
source ~/.bashrc
nvm install --lts
nvm alias default lts/*
node -v && npm -vThe distribution package route, which puts Node under apt and applies security updates alongside everything else:
curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash -
sudo apt install -y nodejs
node -vKeeping the app running with systemd
systemd is already on the machine, restarts your process on crash and on reboot, and logs to journald. For a single application on a single server it is the simplest correct answer. Create a dedicated user, deploy the code, then write the unit.
sudo adduser --system --group --home /var/www/app appuser
sudo chown -R appuser:appuser /var/www/app
sudo nano /etc/systemd/system/app.service[Unit]
Description=Node.js application
After=network.target
[Service]
Type=simple
User=appuser
Group=appuser
WorkingDirectory=/var/www/app
ExecStart=/usr/bin/node /var/www/app/server.js
Environment=NODE_ENV=production
Environment=PORT=3000
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=nodeapp
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload
sudo systemctl enable --now app
sudo systemctl status app
journalctl -u app -fSecrets belong in an environment file rather than the unit, because unit files are world readable. Put them in /etc/app.env with mode 600 owned by the service user and reference it with EnvironmentFile=/etc/app.env.
The PM2 alternative
PM2 is worth it when you want clustering across CPU cores, zero downtime reloads or a built in log rotation story without writing any of it yourself. It runs as a daemon that supervises your processes, and it can generate its own systemd unit so it survives reboots.
npm install -g pm2
cd /var/www/app
pm2 start server.js --name app -i max
pm2 reload app # zero downtime restart
pm2 save
pm2 startup systemd # prints a command to run with sudo
pm2 logs appThe -i max flag starts one worker per CPU core, which is the main practical reason to choose PM2 over a plain systemd unit on a multicore instance. If your app holds state in memory, clustering will break it, so make sessions external before you turn it on.
Nginx as a reverse proxy
Never expose Node directly on port 80. Bind the app to 127.0.0.1:3000 and let Nginx handle TLS, compression, static files and connection limits. This server block also passes the upgrade headers, which WebSockets need.
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 60s;
proxy_cache_bypass $http_upgrade;
}
location /static/ {
alias /var/www/app/public/;
expires 30d;
add_header Cache-Control "public";
}
}sudo ln -s /etc/nginx/sites-available/app /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo certbot --nginx -d app.example.comThen lock the firewall down to the three ports you actually need.
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
sudo ufw status numberedIf your app serves a compiled front end, the static files can go straight to Nginx or a CDN instead of through Node, which is exactly the split described in our guide to deploying Vue.js and the walkthrough on launching Gatsby on Vultr.
Managed Node hosting versus a raw VPS
The whole sequence above exists because you chose a bare server. Managed platforms replace it with two form fields.
| Concern | Raw VPS | Managed Node platform |
|---|---|---|
| Cost | $5 to $6 per month for 1 GB | Free static tiers, services from about $7 per month |
| Process supervision | You write the systemd unit | Handled, with automatic restarts |
| TLS certificates | certbot plus renewal cron | Issued and renewed for you |
| Deploys | Your own script or CI job | Push to Git and it builds |
| Control | Total, including background workers and cron | Constrained to the platform’s model |
Pick managed hosting when the app is a single web service and your time is worth more than the difference in bill. Pick a VPS when you need cron jobs, long running workers, a local database, unusual ports or simply predictable cost at scale. Running several services on one box is where a VPS pays off, as in our guide to installing Grafana on Hostinger or the Discourse on DigitalOcean tutorial.
Troubleshooting a Node deployment
The app dies when I log out. You started it with node server.js in the shell. Nothing is supervising it. Move to the systemd unit or PM2 above.
502 Bad Gateway from Nginx. Node is not listening where the proxy expects. Check with ss -tlnp | grep 3000, confirm the app binds to 127.0.0.1 and not to a container only interface, and read journalctl -u app -n 50.
systemd says the executable was not found. The nvm installed binary is under a home directory that systemd does not resolve. Use an absolute path in ExecStart, found with which node as the service user.
The build runs out of memory on a 1 GB instance. Bundlers are memory hungry. Add a 2 GB swap file, or build in CI and deploy only the output. Building on the smallest instance is a false economy.
WebSockets connect then immediately drop. The proxy is missing the upgrade headers, or proxy_read_timeout is too low. Both are in the server block above.
Frequently asked questions
Which Node.js version should I install on a server?
The current Active LTS, which is Node 24 as of September 2026. Node 22 remains supported in maintenance and is safe to keep running. Node 20 has reached end of life and no longer receives security fixes, so migrate off it.
Do I need PM2 if I already use systemd?
No. systemd restarts crashed processes and starts them at boot, which covers most single service deployments. Add PM2 when you specifically want cluster mode across CPU cores, zero downtime reloads or its log management, not by default.
Should Node listen on port 80 directly?
No. Binding below port 1024 needs elevated privileges, and you lose the TLS termination, compression, static file serving and rate limiting that a reverse proxy gives you for free. Bind to a high port on localhost and proxy through Nginx.
How much RAM does a Node.js app need?
A small API runs fine in 1 GB, but builds are the pressure point rather than runtime. Either add swap so bundling does not get killed, or build elsewhere and ship the artifact. Move to 2 GB once you run a database on the same server.
Is nvm safe to use in production?
Yes, provided the service user owns the installation and your unit file points at the absolute binary path. The common failure is installing as root and running as another user, which makes the binary invisible to the service.
The bottom line
Installing Node.js on cloud hosting is four decisions, not one. Choose an LTS version and pin it. Install it in a way the service user can actually reach. Put a supervisor in front of the process so it survives crashes and reboots. Put Nginx in front of that so you get TLS and sane defaults.
If none of those four steps sound like work you want to own, a managed platform does all of it for roughly the price of the VPS. The moment you need workers, cron, a colocated database or several services on one machine, come back to the server and run the sequence above. It takes ten minutes and it holds up for years.
