Introduction
Deploying a Node.js app shouldn't cost $50/mo on Heroku or require a DevOps degree. With a UK VPS from Hostingowy, you can deploy any Node.js application — Express, Fastify, Next.js, or NestJS — in under 15 minutes for as little as $5/mo.
In this guide, you'll set up a production-grade Node.js deployment with: - PM2 process manager (auto-restart on crash) - NGINX reverse proxy with SSL - PostgreSQL database - GitHub Actions CI/CD - Monitoring and log rotation
---
Step 1: Provision Your VPS
Log into Hostingowy.com and deploy a VPS:
- OS: Ubuntu 24.04 LTS
- Plan: Starter ($5/mo) for small apps, Business ($12/mo) for production
- Location: UK (London datacentre)
Once deployed, you'll receive your root password and IP address.
Step 2: Initial Server Setup
SSH into your server:
ssh root@your-vps-ip
Run these essential security steps:
# Update packages
apt update && apt upgrade -y
Create a deploy user
adduser deploy
usermod -aG sudo deploy
Copy SSH key
mkdir -p /home/deploy/.ssh
cp ~/.ssh/authorized_keys /home/deploy/.ssh/
chown -R deploy:deploy /home/deploy/.ssh
Harden SSH
sed -i 's/PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
systemctl restart sshd
Step 3: Install Node.js
Install Node.js 20 LTS:
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
apt install -y nodejs
node --version # Should show v20.x
npm --version # Should show 10.x
Install PM2 globally:
npm install -g pm2
pm2 startup systemd
Step 4: Install and Configure PostgreSQL
apt install -y postgresql
systemctl start postgresql
systemctl enable postgresql
Create database
sudo -u postgres createuser --interactive # Create user: app_user
sudo -u postgres createdb app_db -O app_user
Set a password for your database user:
sudo -u postgres psql -c "ALTER USER app_user WITH PASSWORD 'secure_password';"
Step 5: Deploy Your Application
On your local machine, prepare your app for deployment. Example ecosystem.config.js for PM2:
module.exports = {
apps: [{
name: 'myapp',
script: 'server.js',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
PORT: 3000,
DATABASE_URL: 'postgresql://app_user:secure_password@localhost:5432/app_db'
},
max_memory_restart: '500M',
log_date_format: 'YYYY-MM-DD HH:mm:ss',
error_file: '/var/log/myapp/error.log',
out_file: '/var/log/myapp/out.log',
merge_logs: true
}]
};
Deploy the code to your server:
# From your local machine
rsync -avz --exclude 'node_modules' --exclude '.env' ./app/ deploy@your-vps-ip:/home/deploy/myapp/
SSH into server
ssh deploy@your-vps-ip
Install dependencies and start
cd /home/deploy/myapp
npm install --production
pm2 start ecosystem.config.js
pm2 save
Step 6: Set Up NGINX Reverse Proxy with SSL
Install NGINX:
apt install -y nginx
Create the site configuration at /etc/nginx/sites-available/myapp:
server {
listen 80;
server_name yourdomain.com;
location / {
proxy_pass http://localhost: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;
proxy_read_timeout 60s;
}
}
Enable the site and get SSL:
ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
Install SSL with Certbot
apt install -y certbot python3-certbot-nginx
certbot --nginx -d yourdomain.com --non-interactive --agree-tos -m admin@yourdomain.com
Step 7: Set Up CI/CD with GitHub Actions
Create .github/workflows/deploy.yml in your repository:
name: Deploy to UK VPS
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install dependencies
- run: npm ci --production
- name: Deploy via rsync
- uses: easingthemes/ssh-deploy@v4.1.0
- with:
- ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
- remote-host: ${{ secrets.VPS_HOST }}
- remote-user: deploy
- source: "."
- target: "/home/deploy/myapp"
- name: Restart application
- uses: appleboy/ssh-action@v1.0.3
- with:
- host: ${{ secrets.VPS_HOST }}
- username: deploy
- key: ${{ secrets.SSH_PRIVATE_KEY }}
- script: |
- cd /home/deploy/myapp
- pm2 reload ecosystem.config.js
Step 8: Monitoring and Logs
Health checks with UptimeRobot
Get notified within 5 minutes if your app goes down:
- Create an UptimeRobot account
- Add a HTTP monitor for
https://yourdomain.com - Set check interval to 5 minutes
- Add SMS/email alert contacts
View logs
# PM2 logs
pm2 logs myapp --lines 100
System logs
journalctl -u nginx --since "1 hour ago"
Application error log
tail -f /var/log/myapp/error.log
Auto-healing
PM2 restarts your app automatically if it crashes. Add a health check endpoint:
// server.js
app.get('/health', (req, res) => {
res.json({ status: 'ok', uptime: process.uptime() });
});
Then configure PM2 to check it:
// ecosystem.config.js
module.exports = {
apps: [{
name: 'myapp',
script: 'server.js',
instances: 'max',
exec_mode: 'cluster',
max_memory_restart: '500M',
// Auto-healing health check
min_uptime: '10s',
max_restarts: 10,
restart_delay: 5000
}]
};
Cost Comparison: UK VPS vs Heroku vs DigitalOcean Apps
| Service | 1 GB RAM / 1 vCPU | 4 GB RAM / 2 vCPU | Notes |
|---|---|---|---|
| Hostingowy Starter/Business | $5/mo | $12/mo | Full root access, UK datacentre |
| Heroku Basic | $25/mo | $50/mo | No root, ephemeral storage |
| DigitalOcean App Platform | $24/mo | $48/mo | Limited customisation |
| Railway | $12/mo | $36/mo | No SSH access |
| AWS Elastic Beanstalk | ~$15/mo | ~$45/mo | Complex setup, surprise fees |
Annual saving with Hostingowy Business plan vs Heroku: $456/year.
Troubleshooting
App crashes immediately
pm2 logs myapp --lines 50 # Check error output
journalctl -u nginx # Check for port conflicts
Cannot connect to database
# Check PostgreSQL status
systemctl status postgresql
sudo -u postgres psql -c "\l"
SSL certificate not renewing
certbot renew --dry-run
Check port 80 is accessible from internet
NGINX 502 Bad Gateway
# App is likely not running
pm2 list
Restart if needed
pm2 restart myapp
Next Steps
Your Node.js app is now deployed on a UK VPS with: - ✅ Automatic restarts (PM2) - ✅ SSL/TLS (Certbot) - ✅ Auto-deploy from GitHub - ✅ Database (PostgreSQL) - ✅ Monitoring (UptimeRobot) - ✅ UK data residency (GDPR compliant)
Ready to deploy? Use code LAUNCH100 for your first month free.