Home / Guides / Node.js

Deploy a Node.js app in 15 minutes

No Docker, no PaaS, no magic. Node.js plus systemd and Nginx is a stack that survives reboots, crashes and traffic — and you can read all of it.

1. Install Node.js

apt update && apt install -y curl
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
apt install -y nodejs
node --version

2. Create an app user

Never run a web app as root. A separate user limits the damage if the app is ever compromised.

adduser --system --group --home /opt/myapp app
mkdir -p /opt/myapp
chown app:app /opt/myapp

3. Upload the app

From your laptop, with rsync over SSH — no Git server required:

rsync -avz --exclude node_modules ./app/ root@YOUR_SERVER_IP:/opt/myapp/

On the server, install dependencies and test once in the foreground:

cd /opt/myapp
sudo -u app npm ci --omit=dev
sudo -u app PORT=3000 node server.js

The app must listen on 127.0.0.1:3000 — not on the public interface. Nginx will face the internet.

4. systemd keeps it alive

cat > /etc/systemd/system/myapp.service <<'EOF'
[Unit]
Description=My Node.js app
After=network-online.target
[Service]
User=app
WorkingDirectory=/opt/myapp
Environment=NODE_ENV=production
Environment=PORT=3000
ExecStart=/usr/bin/node server.js
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now myapp
systemctl status myapp

Crash? It restarts in three seconds. Reboot? It starts automatically. Deploy a new version? rsync, then systemctl restart myapp.

5. Nginx in front

apt install -y nginx
cat > /etc/nginx/sites-available/myapp <<'EOF'
server {
    listen 80;
    server_name example.com;
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
EOF
ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/myapp
nginx -t && systemctl reload nginx

Then point your domain's A record at the server and add HTTPS — the full walkthrough is in the Nginx and HTTPS guide.

6. Check the logs

journalctl -u myapp -f --no-pager
tail -f /var/log/nginx/error.log

Console output from your app lands in journald automatically — no log files to configure.

Quick answers

Do I need PM2?

Not really. systemd does everything PM2 does for a single server — auto-restart, boot start, logs — without an extra dependency. PM2 makes sense mainly if you run several Node processes with clustering.

How much RAM does a Node app need?

A typical Express app idles around 80–150 MB. A 2 GB server runs it comfortably next to Nginx and PostgreSQL; build steps like webpack are the memory-hungry part, so build in CI or on your laptop.

How do I deploy a new version without downtime?

rsync the new code, then restart — the gap is under a second, because systemd restarts the process immediately. For zero-downtime you would run two ports and switch Nginx upstream, but most projects never need it.

This stack fits comfortably in Buran-1 at €6.5/mo.

Related