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
- New Project → New Server
- Location: Falkenstein (EU) or Ashburn (US East) depending on your users
- Image: Ubuntu 22.04
- Type: CX22 (2 vCPU, 4GB RAM, 40GB NVMe)
- SSH Key: paste your public key (
cat ~/.ssh/id_ed25519.pub) - 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.