Why Deploy Node.js on a VPS Rather Than on a Managed Platform?
Serverless and PaaS platforms are convenient to start with, but their cost climbs with traffic, and they impose constraints (timeouts, cold starts, WebSocket connection limits). A VPS gives you a persistent Node.js process, long-lived connections (WebSocket, SSE) with no artificial limit, dedicated resources, and a predictable fixed cost. You decide on the Node version, the process manager, the reverse proxy, and the update strategy, without depending on a provider's conventions.
What You Gain with a VPS for Node.js
- Persistent Node.js process, ideal for WebSocket, SSE, and long-running tasks with no cold start
- Automatic restart on crash and on server reboot thanks to PM2
- Cluster mode to use all the VPS's CPU cores without changing your code
- Nginx reverse proxy in front of Node for HTTPS, compression, and static files
- Fixed, predictable cost, with no per-request billing or scaling surprises
- Full control over the Node.js version and system dependencies
Prerequisites
A standard Node.js application (Express, Nest, or Fastify API) runs comfortably on a VPS with 1 to 2 vCPU and 1 to 2 GB of RAM; plan for more RAM if you compile a heavy frontend at build time or run workers. Install Node.js via nvm or the NodeSource repository to pin an LTS version, along with PM2 as the process manager and Nginx as the reverse proxy. You need a domain pointing to the VPS IP and ports 80/443 open.
Deploy a Node.js Application to Production
Prepare the VPS and Install Node.js
On an Ubuntu 22.04 VPS, update the system and install an LTS version of Node via nvm (nvm install --lts) or the NodeSource repository. Check with: node -v and npm -v. Create a non-root user dedicated to the application rather than running everything as root.
Fetch the Code and Build
Clone your repository on the VPS, install the production dependencies (npm ci --omit=dev), and run the build if needed (npm run build). Put sensitive variables in an uncommitted .env file or in the service's environment variables.
Launch the Application with PM2
Install PM2 globally (npm install -g pm2), then start the app: pm2 start app.js --name monapp -i max. The -i max flag enables cluster mode across all cores. PM2 automatically restarts the process if it crashes.
Enable Automatic Startup at Boot
Generate the systemd script with pm2 startup, run the displayed command, then freeze the current state with pm2 save. On the next VPS reboot, your application will be restarted automatically, with no intervention.
Configure Nginx as a Reverse Proxy
Install Nginx and create a virtual host for your domain that proxies to Node's local port (e.g., proxy_pass http://localhost:3000;). Add the Upgrade and Connection headers if you use WebSocket. Test the config (nginx -t), then reload.
Secure with HTTPS Using Let's Encrypt
Install Certbot and its Nginx plugin, then run certbot --nginx -d yourdomain.com. Certbot obtains the certificate, configures the HTTP→HTTPS redirect, and schedules automatic renewal. Your Node application is now served over HTTPS.
For zero-downtime deployments, combine PM2 cluster mode with pm2 reload monapp rather than restart: PM2 restarts the workers one by one, always keeping at least one instance available during the update. Then monitor memory with pm2 monit and set an automatic restart limit (--max-memory-restart 500M) to prevent memory leaks in production.