Deploying a Node.js application on a VPS in India
13 min read · 05-Sept-2025
villagehosting.in team
5 September 2025
Node.js applications cannot run on standard shared cPanel hosting — they need a persistent process. A VPS is the right home for Node.js. Here is the full deployment stack, from server setup to HTTPS.
Why Node.js needs a VPS
Shared hosting runs PHP as short-lived processes invoked per request. Node.js is a long-running server process that listens on a port. Shared hosting doesn't allow persistent processes or custom port binding — a VPS with 2 GB RAM is the minimum viable environment for a production Node.js application.
What you need
- A Linux VPS (Ubuntu 22.04 LTS recommended)
- A domain name pointed at your VPS IP
- SSH access to the server
Step 1: Install Node.js via nvm
Never install Node.js from Ubuntu's default package manager — it is always outdated. Use nvm (Node Version Manager) instead:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
source ~/.bashrc
nvm install --lts
nvm use --lts
node --version # should show v20.x or later
Step 2: Upload your application
Clone from Git (recommended):
cd ~/
git clone https://github.com/youruser/your-app.git
cd your-app
npm install --production
Or upload via SFTP (FileZilla, Transmit) if you are not using Git.
Step 3: Configure environment variables
Create a .env file in your project root:
nano ~/your-app/.env
NODE_ENV=production
PORT=3000
DATABASE_URL=mysql://user:password@localhost/dbname
SESSION_SECRET=your-long-random-secret
Never commit .env to Git. Add it to .gitignore.
Step 4: Test your application runs
cd ~/your-app
node server.js # or whatever your entry point is
Visit http://your-server-ip:3000 to confirm. Then press Ctrl+C — you will run it properly via PM2 next.
Step 5: Install PM2 as the process manager
PM2 keeps your Node.js application running after you close SSH, restarts it on crashes, and starts it on server reboot.
npm install -g pm2
# Start your app
pm2 start ~/your-app/server.js --name "myapp"
# Save so it restarts on reboot
pm2 save
pm2 startup # run the command it outputs to enable auto-start
Useful PM2 commands:
pm2 status # see all running processes
pm2 logs myapp # stream application logs
pm2 restart myapp # restart after code changes
pm2 stop myapp # stop the app
pm2 monit # real-time CPU/memory dashboard
Step 6: Install NGINX as a reverse proxy
Your Node.js app listens on port 3000. NGINX sits in front, accepting traffic on port 80/443 and forwarding it to Node.js.
sudo apt update
sudo apt install -y nginx
sudo systemctl enable nginx
sudo systemctl start nginx
Create an NGINX site config:
sudo nano /etc/nginx/sites-available/myapp
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
location / {
proxy_pass http://localhost: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_cache_bypass $http_upgrade;
}
}
Enable the site:
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t # test config
sudo systemctl reload nginx
Step 7: Get a free SSL certificate
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
Certbot modifies your NGINX config to add HTTPS and redirects. It also sets up auto-renewal.
Verify auto-renewal works:
sudo certbot renew --dry-run
Step 8: Configure UFW firewall
sudo ufw allow ssh
sudo ufw allow 'Nginx Full'
sudo ufw enable
sudo ufw status
Do not expose port 3000 directly — all traffic should go through NGINX.
Deploying updates
After pushing new code to your repository:
cd ~/your-app
git pull
npm install --production
pm2 restart myapp
For zero-downtime deploys (if your app can handle graceful shutdown):
pm2 reload myapp # rolling restart, keeps old instance alive until new one is ready
Setting up a deploy script
Create ~/deploy.sh:
#!/bin/bash
set -e
cd ~/your-app
git pull origin main
npm install --production
pm2 reload myapp --update-env
echo "Deployed at $(date)"
chmod +x ~/deploy.sh
Then deploy from your local machine:
ssh user@yourserver "~/deploy.sh"
Running a Next.js application
Next.js has a built-in server. The setup is nearly identical:
cd ~/your-nextjs-app
npm run build
pm2 start npm --name "nextapp" -- start
The npm start command runs next start which defaults to port 3000.
Running multiple Node.js apps on one VPS
NGINX can host multiple apps on different domains or paths:
# App 1 on port 3000
server {
server_name app1.com;
location / { proxy_pass http://localhost:3000; }
}
# App 2 on port 3001
server {
server_name app2.com;
location / { proxy_pass http://localhost:3001; }
}
Start each app on a different port with PM2 and set PORT=3001 in the second app's .env.
Monitoring and logs
# View PM2 logs (last 100 lines)
pm2 logs myapp --lines 100
# View NGINX access log
sudo tail -f /var/log/nginx/access.log
# View NGINX error log
sudo tail -f /var/log/nginx/error.log
Set up a monitoring tool like PM2 Plus or a simple UptimeRobot ping to get alerted when your app goes down.