TakeHost
← All tutorials

Server Management

How to Install Node.js and Run Apps Safely

Intermediate14 minNode.jssystemdpm2NginxSecurity

Node.js runs JavaScript on the server. This guide installs the current LTS from the NodeSource apt repository (with its GPG key) or via nvm per user, then runs your app as a dedicated non-root user behind an Nginx reverse proxy. Exposing a Node process directly as root is a serious risk.

/01

Install Node.js from NodeSource (with GPG)

Add the NodeSource repository, whose setup script installs a verified GPG key, then install the current LTS. Alternatively, use nvm for a per-user install that never needs root.

# The NodeSource setup script adds a GPG-verified apt repo (it does NOT pipe an app into a root shell)
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs
# Per-user alternative (no sudo, isolates Node to your account):
# curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash && nvm install --lts
/02

Verify the Installation

Confirm Node and npm report sane versions.

node -v   # should print v22.x (current LTS)
npm -v
/03

Create a Dedicated Non-Root App User

Run your application under its own locked-down account so a bug in your app cannot become root on the host.

sudo useradd -r -m -d /opt/myapp -s /usr/sbin/nologin nodeapp
sudo cp -r ./your-app/* /opt/myapp/
sudo chown -R nodeapp:nodeapp /opt/myapp
/04

Run the App with systemd (Recommended)

A systemd unit keeps the app running, restarts it on crash, starts it on boot, and runs it as the nodeapp user. This is cleaner than running things by hand.

sudo nano /etc/systemd/system/myapp.service
# Paste:
[Unit]
Description=My Node App
After=network.target

[Service]
User=nodeapp
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/node /opt/myapp/app.js
Restart=on-failure
Environment=NODE_ENV=production

[Install]
WantedBy=multi-user.target

# Then:
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
sudo systemctl status myapp --no-pager   # verify it is running
# pm2 alternative (as the nodeapp user): pm2 start app.js --name myapp && pm2 save && pm2 startup
/05

Put Nginx in Front as a Reverse Proxy

Keep Node listening on localhost and let Nginx handle the public port and TLS. This shields the app and gives you a place to terminate HTTPS.

sudo apt install -y nginx
sudo nano /etc/nginx/sites-available/myapp
# Inside the server { } block:
#   location / {
#       proxy_pass http://127.0.0.1:3000;   # your app's local port
#       proxy_set_header Host $host;
#       proxy_set_header X-Forwarded-For $remote_addr;
#   }
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
/06

Add TLS and Firewall

Issue a certificate for the proxy and open only web ports, keeping the Node port private to localhost.

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d yourdomain.com --redirect
sudo ufw allow 'Nginx Full'   # do NOT open the Node port (3000); it stays on localhost

Node.js is installed from a GPG-verified repository, your app runs as a non-root user under systemd, and it sits behind Nginx with TLS while its own port stays bound to localhost. Never run application code as root, and keep Node on a supported LTS line for security updates.

Ready when you are

Deploy it on TakeHost.