How to Set Up a Hetzner CX22 VPS: Step-by-Step Guide (2026)

Published

How to Set Up a Hetzner CX22 VPS: Step-by-Step Guide (2026)

If you’ve already looked at the Hetzner CX22 pricing and decided it’s the right server, this guide gets you from a fresh VPS to a live Node.js app — SSH locked down, firewall on, Nginx handling SSL, PM2 keeping your app alive.

What you’ll have at the end:

  • Non-root user with SSH key login only
  • UFW firewall (only ports 22, 80, 443 open)
  • Node.js v22 LTS via nvm
  • PM2 in cluster mode with startup on reboot
  • Nginx reverse proxy with free SSL via Certbot

Time required: ~25 minutes.


Step 1: Create the Server

In Hetzner Cloud Console:

  1. New Project → New Server
  2. Location: Falkenstein (EU) or Ashburn (US East) depending on your users
  3. Image: Ubuntu 22.04
  4. Type: CX22 (2 vCPU, 4GB RAM, 40GB NVMe)
  5. SSH Key: paste your public key (cat ~/.ssh/id_ed25519.pub)
  6. Click Create & Buy

Your server is ready in ~30 seconds. Note the IP address.


Step 2: First Login and Create a Non-Root User

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

# Create a non-root user
adduser deploy
usermod -aG sudo deploy

# Copy SSH keys to new user
rsync --archive --chown=deploy:deploy ~/.ssh /home/deploy

# Test the new user in a separate terminal before closing root session
ssh deploy@YOUR_SERVER_IP

From now on, always use deploy (or your chosen username), never root.


Step 3: Secure the Server

# Update packages
sudo apt update && sudo apt upgrade -y

# Install UFW firewall
sudo apt install ufw -y

# Allow only essential ports
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

# Verify
sudo ufw status

Expected output:

Status: active
To                         Action      From
--                         ------      ----
OpenSSH                    ALLOW       Anywhere
80/tcp                     ALLOW       Anywhere
443/tcp                    ALLOW       Anywhere

Optional but recommended — disable password login (SSH key only):

sudo nano /etc/ssh/sshd_config
# Set: PasswordAuthentication no
sudo systemctl restart ssh

Step 4: Install Node.js via nvm

# Install nvm
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
source ~/.bashrc

# Install Node.js LTS
nvm install --lts
nvm use --lts

# Verify
node -v   # v22.x.x
npm -v

Using nvm instead of apt install nodejs lets you switch Node versions without sudo and avoids the outdated versions in Ubuntu’s package registry.


Step 5: Install PM2

npm install -g pm2

# Create a 2GB swap file (essential on 4GB VPS for deploy spikes)
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

For details on PM2 cluster mode configuration for CX22, see the PM2 VPS optimization guide.


Step 6: Deploy Your App

# Clone your repo
git clone https://github.com/yourname/your-app.git /var/www/your-app
cd /var/www/your-app
npm install --omit=dev
npm run build  # if needed

# Start with PM2
pm2 start ecosystem.config.js --env production

# Save and enable startup
pm2 save
pm2 startup
# Run the command it prints, e.g.:
# sudo env PATH=... pm2 startup systemd -u deploy --hp /home/deploy

A minimal ecosystem.config.js for CX22 (2 cores):

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

Step 7: Nginx Reverse Proxy + SSL

# Install Nginx and Certbot
sudo apt install nginx certbot python3-certbot-nginx -y

# Create server block
sudo nano /etc/nginx/sites-available/your-app

Paste this config:

server {
    listen 80;
    server_name yourdomain.com www.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_cache_bypass $http_upgrade;
    }
}
# Enable the site
sudo ln -s /etc/nginx/sites-available/your-app /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

# Get free SSL certificate
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

Certbot automatically modifies your Nginx config for HTTPS and sets up auto-renewal.


Verify Everything Works

# Check PM2 status
pm2 status

# Check Nginx
sudo systemctl status nginx

# Check your app is responding
curl http://localhost:3000

# Monitor live
pm2 monit

What’s Next

Your CX22 is now production-ready. For zero-downtime deployments using GitHub Actions, see the Zero-Downtime Node.js Deploy guide.

For a full breakdown of Hetzner CX22 vs DigitalOcean performance, see the benchmark comparison.