Deploying a Node.js application on a VPS (Virtual Private Server) gives you full control over performance, security, and scalability. With Maavee Systems, you also get built-in infrastructure advantages like a perimeter firewall, optional static IPs, and managed SSL via Maavee Load Balancer.
This guide shows both:
Maavee Load Balancer is a specially configured software load balancer designed to maintain high-speed, reliable traffic. It protects you from complex configuration issues and ensures that whatever you build here remains portable across any cloud provider in the world.
ssh root@your-server-ip
sudo apt update && sudo apt upgrade -y
const express = require('express');const app = express();const PORT = process.env.PORT || 3000;
// Middleware to parse JSON bodiesapp.use(express.json());
// 1. A basic GET route for the homepageapp.get('/', (req, res) => { res.send('<h1>Welcome to the Node.js Web App</h1><p>The server is running smoothly.</p>');});
// 2. A JSON API routeapp.get('/api/status', (req, res) => { res.json({ status: 'online', timestamp: new Date().toISOString(), uptime: process.uptime() });});
// 3. A POST route to receive dataapp.post('/api/data', (req, res) => { const receivedData = req.body;
if (!receivedData.name) { return res.status(400).json({ error: 'Please provide a name.' }); }
res.status(201).json({ message: 'Data received successfully', yourData: receivedData });});
// 4. Handle 404 - Keep this at the end of your routesapp.use((req, res) => { res.status(404).send('Resource Not Found');});
// Start the serverapp.listen(PORT, '0.0.0.0', () => { console.log(`Server is running at http://localhost:${PORT}`);});
This is the main entry point for your application. app.js
At Maavee, we encourage the use of Forgejo, but you can choose how your code is delivered to the server. A Forgejo runner agent active on the target server is often the ideal solution.
Create a separate user for running the Node.js application. Ensure it is configured with a non-login shell.
groupadd runner
useradd -g runner -m -d /opt/runner -s /usr/sbin/nologin runner
Switch to the new user by specifying a login shell:
su - runner -s /bin/bash
Install Node.js within the user account; avoid installing it globally system-wide.
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh | bash
Reload your shell (or log out and log back in via SSH) to load nvm.
nvm install 24
At the time of writing, Node.js version 24 is the latest stable LTS version.
Install PM2 globally within the user account:
npm install -g pm2
Configure the Forgejo runner (you can use the screen utility or a systemd unit file to keep the runner active). Forgejo Config file
forgejo-runner daemon -c $HOME/forgejo-config.yml
A Forgejo Actions workflow can pull the code to the relevant directory and instruct PM2 to stop or restart the service. It's easy for anyone to find information on how to write a Forgejo actions workflow.
name: Deploy Nodejs Application
on: push: branches: - 'master'
jobs: build-and-test: runs-on: nodejs-tute
steps: - name: Checkout code uses: actions/checkout@v4 with: lfs: true
- name: Show insight run: | ls -l pwd
- name: Copy content to user run: | mkdir -p ${HOME}/nodejs-tutes rsync -avz --delete \ --exclude '.git*' \ ./ ${HOME}/nodejs-tutes/
- name: Install npm packages run: | cd ${HOME}/nodejs-tutes npm install
- name: Restart service run: | cd ${HOME}/nodejs-tutes pm2 restart all
Use this if:
Use this if:
0.0.0.0:3000To use this setup effectively:
Maavee provides multi-layered protection:
You control the rules inside the VPS:
pm2 start app.js
pm2 startup
pm2 save
To make your application accessible via a custom domain, you have two primary paths depending on your chosen architecture:
Point your domain's A Record directly to your VPS’s public IP address. If using Nginx, you will then manage SSL certificates locally using Certbot.
Point your domain's CNAME Record to loadb.maavee.com. The Maavee Load Balancer will automatically handle SSL termination and route traffic to your VPS. This ensures your application remains resilient even if your underlying VPS IP changes.
Beyond the firewall, we recommend these essential steps to secure your production environment:
Disable password-based logins and use SSH keys for all access.
ssh-copy-id runner@your-server-ip/etc/ssh/sshd_config:
PasswordAuthentication noPermitRootLogin nosudo systemctl restart sshAlways run your application under the dedicated runner user created earlier. Never run Node.js processes as root.
To ensure your application recovers automatically after a server restart, use PM2's startup script generator:
pm2 startup
# Run the command generated by the output above
pm2 save
Production logs can quickly consume disk space. We recommend installing pm2-logrotate:
pm2 install pm2-logrotate
Monitor your application's resource usage and health in real-time:
pm2 monit
Maavee Systems provides flexibility at every layer of your stack:
For most production workloads, the MLB + Managed SSL provides security, simplicity, and high-speed traffic handling. By offloading SSL and load balancing to Maavee's specialized infrastructure, you can focus entirely on building your application.