Hosting Node.js Apps on Bare Metal: Express, Next.js, Fastify
Node.js runs everywhere — but running it well in production requires more than node server.js. This guide covers deploying Express, Next.js, and Fastify on a bare metal server with PM2 process management, Nginx reverse proxy, SSL, and systemd — all for $6/mo on RAW.
Why Bare Metal for Node.js
Node.js is single-threaded by default. On shared cloud VMs, your event loop competes with other tenants for CPU time. On bare metal, your Node process gets dedicated cores with consistent latency. No cold starts, no noisy neighbors, no CPU throttling at the worst possible moment.
A RAW raw-2 ARM server gives you 2 dedicated cores, 4 GB RAM, and 40 GB NVMe for $6/mo. That is enough to run multiple Node.js apps behind Nginx with room to spare.
Step 1: Provision and Install Node.js
# Deploy a RAW server
npx rawhq deploy
# SSH in and install Node.js 22 LTS
ssh root@your-server-ip
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
apt install -y nodejs
# Verify
node -v # v22.x
npm -v # 10.xStep 2: Deploy Your Application
Clone your repo, install dependencies, and build:
# Clone and install
cd /opt
git clone https://github.com/you/your-app.git app
cd app
npm ci --production
# For Next.js, build first
npm run buildStep 3: PM2 Process Manager
PM2 keeps your Node.js app running, restarts on crash, and manages logs. It is the industry standard for Node.js process management on Linux.
# Install PM2 globally
npm install -g pm2
# Start an Express or Fastify app
pm2 start server.js --name my-api -i max
# Start a Next.js production server
pm2 start npm --name my-nextjs -- start
# Save the process list and enable startup
pm2 save
pm2 startup systemdThe -i max flag enables cluster mode, spawning one worker per CPU core. On a 2-core RAW server, that gives you two workers handling requests in parallel — doubling throughput for CPU-bound workloads.
Key PM2 Commands
- pm2 list — show all running processes
- pm2 logs — stream logs from all apps
- pm2 monit — real-time CPU and memory dashboard
- pm2 reload my-api — zero-downtime restart
- pm2 delete my-api — stop and remove a process
Step 4: Nginx Reverse Proxy
Nginx sits in front of Node.js to handle SSL termination, static files, gzip compression, and load balancing. Node.js should never face the internet directly.
# Install Nginx
apt install -y nginx
# Create site config
cat > /etc/nginx/sites-available/my-app << 'EOF'
server {
listen 80;
server_name yourdomain.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;
proxy_cache_bypass $http_upgrade;
}
}
EOF
# Enable and reload
ln -s /etc/nginx/sites-available/my-app /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginxStep 5: SSL with Certbot
# Install Certbot
apt install -y certbot python3-certbot-nginx
# Obtain and auto-configure SSL
certbot --nginx -d yourdomain.com
# Auto-renewal is enabled by default
certbot renew --dry-runCertbot modifies your Nginx config to redirect HTTP to HTTPS and installs the certificate. Renewals happen automatically via systemd timer.
Step 6: Systemd Integration
PM2 handles process management, but systemd ensures PM2 itself starts on boot. The pm2 startup systemd command from Step 3 creates the service file automatically. Verify it works:
# Reboot and verify everything comes back
reboot
# After reconnecting
pm2 list # all processes should be onlineFramework-Specific Notes
Express
Express apps typically listen on a port and are production-ready as-is. Use pm2 start server.js -i max for cluster mode. Set NODE_ENV=production to disable verbose error pages and enable caching.
Next.js
Run npm run build first, then pm2 start npm --name nextjs -- start. Next.js standalone output mode reduces the deployment footprint — set output: 'standalone' in next.config.js to generate a self-contained server.
Fastify
Fastify is already optimized for throughput. Use pm2 start server.js -i max. Ensure you listen on 0.0.0.0 (not localhost) if running behind Nginx on the same machine — or use 127.0.0.1 to restrict direct access.
Cost Comparison: Bare Metal vs PaaS
Vercel works great for hobby projects but hits $20/user/mo the moment you need a team. Railway and Heroku charge per-usage, making costs unpredictable under load. A $6/mo RAW server handles all three frameworks simultaneously with resources to spare.
Performance Tips
- Enable gzip in Nginx — compress JSON and HTML responses before they leave the server
- Use cluster mode — PM2
-i maxutilizes all available CPU cores - Serve static files from Nginx — bypass Node.js entirely for images, CSS, and JS bundles
- Set NODE_ENV=production — disables dev tooling and enables internal optimizations
- Monitor with PM2 monit — watch memory usage and restart leaky processes automatically
Deploy Node.js on RAW
npx rawhq deploy7-day free trial. 13 seconds to deploy. Run Express, Next.js, and Fastify on dedicated hardware for $6/mo.