DEV Community

Cover image for I set up a Node.js production server on Hetzner CX22 for €4.35/mo — here's exactly what I ran
Vpsfordev
Vpsfordev

Posted on • Originally published at vpsfor.dev

I set up a Node.js production server on Hetzner CX22 for €4.35/mo — here's exactly what I ran

Most Node.js hosting guides assume you're using DigitalOcean or AWS. I switched to Hetzner CX22 (€4.35/mo, 2 vCPU, 4GB RAM) and it's been running production workloads for months without a single issue.

Here's the exact setup I use — no fluff, just the commands.

Why Hetzner CX22

A 4GB DigitalOcean droplet costs ~$24/month. The Hetzner CX22 with identical specs costs €4.35/month. I ran benchmarks with autocannon — Hetzner is actually ~3.5% faster. So it's cheaper AND faster. The only trade-off is ticket-only support.

The setup (in order)

1. Create a non-root user immediately

# Login as root (first and only time)
ssh root@YOUR_SERVER_IP

adduser deploy
usermod -aG sudo deploy
rsync --archive --chown=deploy:deploy ~/.ssh /home/deploy
Enter fullscreen mode Exit fullscreen mode

Never run Node.js as root. This takes 30 seconds and saves you from a class of security issues.

2. Firewall on, then forget about it

sudo apt update && sudo apt upgrade -y
sudo apt install ufw -y

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
Enter fullscreen mode Exit fullscreen mode

All ports closed except 22, 80, 443. Hetzner servers are fully open by default — this step is not optional.

3. Node.js via nvm (not apt)

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
source ~/.bashrc
nvm install --lts
Enter fullscreen mode Exit fullscreen mode

apt install nodejs gives you an outdated version. nvm gives you the current LTS without sudo.

4. Swap file before anything else

sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
Enter fullscreen mode Exit fullscreen mode

On a 4GB VPS, npm install during a deploy can spike RAM by 30-50%. Without swap, it OOM-kills your running app mid-deploy. I learned this the hard way.

5. PM2 with memory limits

npm install -g pm2
Enter fullscreen mode Exit fullscreen mode

ecosystem.config.js for a 2-core server:

module.exports = {
  apps: [{
    name: 'my-app',
    script: './dist/index.js',
    instances: 2,           // one per core
    exec_mode: 'cluster',
    max_memory_restart: '350M',
    node_args: '--max-old-space-size=320',
    env_production: { NODE_ENV: 'production', PORT: 3000 }
  }]
};
Enter fullscreen mode Exit fullscreen mode

The --max-old-space-size flag is critical. Without it, V8 can claim up to 1.5GB on a 4GB machine and crash the server. Two workers × 350MB = 700MB max, leaving ~90MB headroom for the OS.

pm2 start ecosystem.config.js --env production
pm2 save
pm2 startup  # run the command it prints
Enter fullscreen mode Exit fullscreen mode

6. Nginx + free SSL

sudo apt install nginx certbot python3-certbot-nginx -y
Enter fullscreen mode Exit fullscreen mode

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

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_cache_bypass $http_upgrade;
    }
}
Enter fullscreen mode Exit fullscreen mode
sudo ln -s /etc/nginx/sites-available/my-app /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d yourdomain.com
Enter fullscreen mode Exit fullscreen mode

Certbot handles HTTPS config and sets up auto-renewal. Done.

Total time

From fresh server to HTTPS Node.js app: ~25 minutes the first time, ~10 minutes once you know the commands.

What I'd do differently

  • Set up SSH key auth only (PasswordAuthentication no in sshd_config) — I skipped this once and had 2,000 failed login attempts in the logs within an hour
  • Monitor with pm2 monit before assuming things are fine — one of my workers had a memory leak that only showed up under load

Full step-by-step guide with exact commands at vpsfor.dev →

Top comments (0)