4 min read Sep 26, 2026

How to Run a Node.js App on Your VPS with PM2 and Nginx

Install Node.js LTS on your VPS, keep your app running with PM2 across crashes and reboots, publish it on your domain with an Nginx reverse proxy and add free HTTPS.

FimuroHost Team

FimuroHost Team

Technical Writer

Share Article

A VPS is a great place for Node.js apps such as Express APIs, Next.js sites, bots and real-time apps. The usual production setup has three parts: Node.js runs your code, PM2 keeps it running and restarts it after crashes or reboots, and Nginx receives visitors on ports 80/443 and forwards them to your app.

Which VPS is this for? These steps are for an Unmanaged VPS, where you log in as root and look after the server yourself. On a Managed VPS we take care of the operating system and server software, and you manage your websites in StackCP instead.

Step 1: Install Node.js (LTS)

Use a current Long Term Support release: Node.js 24 is the active LTS at the time of writing, and Node.js 22 is still supported. The NodeSource repository provides up-to-date packages.

Ubuntu and Debian

curl -fsSL https://deb.nodesource.com/setup_24.x -o nodesource_setup.sh
sudo bash nodesource_setup.sh
sudo apt install -y nodejs
node -v && npm -v

AlmaLinux and Rocky Linux

curl -fsSL https://rpm.nodesource.com/setup_24.x -o nodesource_setup.sh
sudo bash nodesource_setup.sh
sudo dnf install -y nodejs
node -v && npm -v

Alternatively, nvm installs Node per user and makes switching versions easy.

Step 2: Put your app on the server

Run apps as your normal sudo user, never as root:

mkdir -p ~/apps && cd ~/apps
git clone https://github.com/you/myapp.git
cd myapp
npm ci --omit=dev

Make sure the app listens on localhost and a port from the environment, for example in Express:

const port = process.env.PORT || 3000;
app.listen(port, '127.0.0.1', () => console.log(`Listening on ${port}`));

Listening on 127.0.0.1 means only Nginx can reach it; visitors cannot bypass the proxy.

Step 3: Run it with PM2

sudo npm install -g pm2
nano ecosystem.config.js

Paste and adjust:

module.exports = {
  apps: [{
    name: "myapp",
    script: "server.js",
    cwd: "/home/deploy/apps/myapp",
    instances: 1,
    max_memory_restart: "300M",
    env: { NODE_ENV: "production", PORT: 3000 }
  }]
};

Start the app and check it:

pm2 start ecosystem.config.js
pm2 status
pm2 logs myapp

Start automatically after a reboot

pm2 startup systemd

PM2 prints a sudo env PATH=… pm2 startup systemd -u deploy --hp /home/deploy command. Copy and run it exactly, then save the current app list:

pm2 save

Test it with sudo reboot; after a minute, pm2 status should show the app online.

Rotate PM2's own log files so they don't fill the disk: pm2 install pm2-logrotate.

Step 4: Nginx reverse proxy

Install Nginx (sudo apt install nginx or sudo dnf install nginx then sudo systemctl enable --now nginx) and create a site file (/etc/nginx/sites-available/myapp on Ubuntu/Debian, /etc/nginx/conf.d/myapp.conf on AlmaLinux/Rocky):

server {
    listen 80;
    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;
    }
}

The Upgrade and Connection lines let WebSockets (for example Socket.IO) work. Enable and reload:

# Ubuntu / Debian only
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
# all systems
sudo nginx -t && sudo systemctl reload nginx
# AlmaLinux / Rocky only: allow Nginx to connect to your app
sudo setsebool -P httpd_can_network_connect 1

Point app.example.com to the VPS (DNS guide), open ports 80 and 443 in your firewall, then add HTTPS with sudo certbot --nginx -d app.example.com (Certbot guide).

In Express, add app.set('trust proxy', 1) so the app sees the visitor's real IP and HTTPS status.

Deploying updates

cd ~/apps/myapp
git pull
npm ci --omit=dev
npm run build --if-present
pm2 reload myapp

pm2 reload restarts with minimal downtime.

Common problems

  • 502 Bad Gateway: the app isn't running or uses a different port. Check pm2 status, pm2 logs and ss -tlnp | grep 3000.
  • EADDRINUSE: another process already uses the port. Find it with sudo ss -tlnp | grep :3000.
  • App is gone after a reboot: run the pm2 startup command it printed, then pm2 save.
  • App keeps restarting: it's crashing or hitting max_memory_restart. Read pm2 logs myapp --lines 100; add swap or upgrade the plan if memory is short.
  • WebSockets fail: the Upgrade/Connection headers are missing from the Nginx block.

Need help?

If something about the VPS itself is not working (it won't start, you can't reach it, or you need console access, an upgrade or a reinstall), open a support ticket from your client area or message us on WhatsApp at 01818160926. Include your VPS IP address and what you have already tried so we can help faster.

Categories

FimuroHost Team

Written by

FimuroHost Team

Technical Writer