HomeProjectsBlogContact

How to Deploy a Node.js App to a VPS (Step by Step)

From a fresh Linux server to a production Node.js app behind Nginx with HTTPS, a process manager, a firewall and automatic restarts. A complete checklist you can follow and reuse.

How to Deploy a Node.js App to a VPS (Step by Step)

Managed platforms are great, but at some point you'll want, or need, to run an app on your own server. Maybe it's cost, control, WebSockets or a background worker. A virtual private server (VPS) gives you all of that, along with responsibility for keeping it secure.

Here's a reliable path from a fresh Ubuntu server to a production Node.js app. The same pattern works for most backend stacks.

1. Create the server and log in

Pick any VPS provider and a current Ubuntu LTS release. Add your SSH key during setup (SSH keys explained), then:

ssh root@YOUR_SERVER_IP

2. Create a deploy user and update the system

Don't run everything as root.

apt update && apt upgrade -y
adduser deploy
usermod -aG sudo deploy
rsync --archive --chown=deploy:deploy ~/.ssh /home/deploy

Log in as deploy in a new terminal to confirm it works, then disable root and password logins in /etc/ssh/sshd_config (PermitRootLogin no, PasswordAuthentication no) and reload SSH.

3. Turn on the firewall

sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'   # after installing Nginx, or allow 80 and 443
sudo ufw enable

Your Node app itself will listen only on localhost; the world reaches it through Nginx.

4. Install Node.js

Use a version manager or the official distribution packages for a current LTS version. For example, with nvm:

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash
source ~/.bashrc
nvm install --lts

(Read install scripts before piping them to a shell. That's a good habit everywhere.)

5. Get your code onto the server

cd ~
git clone git@github.com:you/your-app.git app
cd app
npm ci --omit=dev
npm run build   # if your app has a build step

Use a deploy key for private repositories.

6. Configure environment variables

Create a .env file on the server (never commit it) or set variables in your process manager config, then lock down its permissions:

chmod 600 .env

(Config best practices.)

7. Run it with a process manager

Your app needs to restart if it crashes and start on boot. Two good options:

PM2:

npm install -g pm2
pm2 start npm --name app -- start
pm2 save
pm2 startup     # prints a command to enable boot startup; run it

Or a systemd service (/etc/systemd/system/app.service):

[Unit]
Description=My Node app
After=network.target

[Service]
User=deploy
WorkingDirectory=/home/deploy/app
EnvironmentFile=/home/deploy/app/.env
ExecStart=/home/deploy/.nvm/versions/node/vXX/bin/node server.js
Restart=always

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now app

Make sure the app listens on 127.0.0.1 and a port like 3000.

8. Put Nginx in front

sudo apt install nginx

Create /etc/nginx/sites-available/app:

server {
    listen 80;
    server_name example.com www.example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        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;
    }
}
sudo ln -s /etc/nginx/sites-available/app /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Point your domain's DNS at the server (DNS guide). More on what Nginx is doing here: reverse proxies explained.

9. Add HTTPS

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
sudo certbot renew --dry-run

Certbot configures Nginx and sets up automatic renewal. (How HTTPS works.)

10. Keep it healthy

  • Automatic security updates: sudo apt install unattended-upgrades.
  • Logs: pm2 logs or journalctl -u app -f. Rotate them so the disk doesn't fill. (Logging and monitoring.)
  • Uptime monitoring: an external check that alerts you when the site goes down.
  • Backups: of the database and any uploaded files. (Backup strategy.)

Deploying updates

A simple deploy script:

cd ~/app
git pull
npm ci --omit=dev
npm run build
pm2 reload app    # or: sudo systemctl restart app

Run it over SSH, or from a GitHub Actions job once you're comfortable. (CI/CD starter.)

FAQ

Do I need Nginx in front of Node.js?

It's strongly recommended. Nginx handles HTTPS, compression, static files, request limits and buffering efficiently, and lets your app run without root privileges on an internal port.

PM2 or systemd for Node.js?

Both work well. PM2 is Node-friendly with built-in log and cluster features; systemd is already on the server and manages any kind of process. Pick one and use it consistently.

How do I deploy without downtime on a single server?

Use pm2 reload with cluster mode, or run two instances behind Nginx and restart them one at a time, so one is always serving traffic.

Keep reading

More from the workbench

All articles →