DevOps & Infrastructure

Complete Hetzner VPS Setup for Web Application Hosting: Production-Ready Guide

Connor O'Dea · · 7 min read
VPS Hetzner Web Hosting DevOps Linux

When we need reliable, cost-effective hosting for client projects at Odea Works, Hetzner consistently delivers. Their VPS offerings provide excellent price-to-performance ratios — crucial when deploying everything from our QuickLotz WMS enterprise system to lightweight AI applications like our Vidmation pipeline.

This guide walks through our complete Hetzner VPS setup web application hosting process, covering everything from initial provisioning to production-ready deployment. Whether you’re hosting a Node.js API, Python web app, or full-stack application, these steps will get you running securely and efficiently.

Why Hetzner for Web Application Hosting

Before diving into setup, here’s why we choose Hetzner over alternatives like AWS or DigitalOcean for many projects:

Cost Efficiency: Hetzner’s pricing is aggressive. A 4GB RAM, 2 vCPU instance costs €4.15/month compared to $24/month on AWS.

European Data Centers: For GDPR compliance and European users, Hetzner’s German data centers provide excellent latency.

Predictable Pricing: No surprise bills. You know exactly what you’ll pay each month.

Solid Performance: We’ve seen consistent performance across multiple production deployments.

The trade-off? Less managed services compared to AWS. But for most web applications, this raw VPS approach actually provides more control and better economics.

Initial Hetzner VPS Provisioning

1. Server Selection

Log into Hetzner Cloud Console and create a new server. For web application hosting, we typically recommend:

  • Location: Choose closest to your users (Nuremberg, Helsinki, Ashburn)
  • Image: Ubuntu 22.04 LTS (most stable for production)
  • Type: Start with CPX21 (4GB RAM, 2 vCPU) — can scale up later
  • SSH Key: Add your public key during creation
# Generate SSH key if you don't have one
ssh-keygen -t ed25519 -C "your-email@domain.com"

2. Initial Connection and Updates

Once provisioned, connect to your server:

ssh root@YOUR_SERVER_IP

First priority: update the system and configure automatic security updates:

# Update package list and upgrade
apt update && apt upgrade -y

# Install essential packages
apt install -y curl wget git unzip ufw fail2ban

# Enable automatic security updates
apt install -y unattended-upgrades
dpkg-reconfigure -plow unattended-upgrades

Security Hardening

Security isn’t optional for production hosting. Here’s our standard hardening process:

1. Create Non-Root User

Never run applications as root:

# Create new user
adduser deploy
usermod -aG sudo deploy

# Copy SSH keys
mkdir -p /home/deploy/.ssh
cp ~/.ssh/authorized_keys /home/deploy/.ssh/
chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keys

2. Configure SSH Security

Edit SSH configuration:

nano /etc/ssh/sshd_config

Key security changes:

# Disable root login
PermitRootLogin no

# Use SSH keys only
PasswordAuthentication no
PubkeyAuthentication yes

# Change default port (optional but recommended)
Port 2222

# Limit login attempts
MaxAuthTries 3

Restart SSH service:

systemctl restart sshd

3. Configure Firewall

# Default policies
ufw default deny incoming
ufw default allow outgoing

# Allow SSH (use your custom port if changed)
ufw allow 2222/tcp

# Allow HTTP and HTTPS
ufw allow 80/tcp
ufw allow 443/tcp

# Enable firewall
ufw enable

4. Configure Fail2Ban

Edit Fail2Ban configuration:

nano /etc/fail2ban/jail.local
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5

[sshd]
enabled = true
port = 2222
logpath = /var/log/auth.log
maxretry = 3

Start and enable Fail2Ban:

systemctl enable fail2ban
systemctl start fail2ban

Web Server Setup

For most applications, we use Nginx as a reverse proxy. It’s fast, reliable, and handles SSL termination well.

Install and Configure Nginx

apt install -y nginx

# Start and enable Nginx
systemctl start nginx
systemctl enable nginx

Create a basic configuration for your application:

nano /etc/nginx/sites-available/your-app
server {
    listen 80;
    server_name your-domain.com www.your-domain.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;
    }
}

Enable the site:

ln -s /etc/nginx/sites-available/your-app /etc/nginx/sites-enabled/
nginx -t
systemctl reload nginx

SSL Certificate Setup

Never deploy without HTTPS. We use Certbot for free SSL certificates:

# Install Certbot
apt install -y certbot python3-certbot-nginx

# Get certificate
certbot --nginx -d your-domain.com -d www.your-domain.com

# Test auto-renewal
certbot renew --dry-run

Certbot automatically modifies your Nginx configuration to handle SSL.

Application Runtime Environment

Node.js Setup

For Node.js applications (like our QuickLotz WMS system):

# Install Node.js via NodeSource
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
apt install -y nodejs

# Install PM2 for process management
npm install -g pm2

# Verify installation
node --version
npm --version

Python Setup

For Python applications (like our Vidmation pipeline):

# Install Python and pip
apt install -y python3 python3-pip python3-venv

# Install system dependencies for common packages
apt install -y python3-dev build-essential libpq-dev

# Create virtual environment
sudo -u deploy python3 -m venv /home/deploy/venv

Database Setup

PostgreSQL

For most production applications, we use PostgreSQL:

# Install PostgreSQL
apt install -y postgresql postgresql-contrib

# Start and enable PostgreSQL
systemctl start postgresql
systemctl enable postgresql

# Create application database and user
sudo -u postgres psql
CREATE DATABASE your_app_db;
CREATE USER your_app_user WITH PASSWORD 'secure_password';
GRANT ALL PRIVILEGES ON DATABASE your_app_db TO your_app_user;
\q

Redis (Optional)

For caching and session storage:

apt install -y redis-server

# Configure Redis
nano /etc/redis/redis.conf

# Set password
requirepass your_redis_password

# Restart Redis
systemctl restart redis-server

Application Deployment

Here’s how we typically deploy applications on our Hetzner setups:

1. Application Structure

# Create application directory
sudo -u deploy mkdir -p /home/deploy/apps/your-app
cd /home/deploy/apps/your-app

# Clone repository
sudo -u deploy git clone https://github.com/your-username/your-app.git .

2. Environment Configuration

Create environment file:

sudo -u deploy nano /home/deploy/apps/your-app/.env
NODE_ENV=production
PORT=3000
DATABASE_URL=postgresql://your_app_user:secure_password@localhost:5432/your_app_db
REDIS_URL=redis://localhost:6379

3. Install Dependencies and Build

For Node.js:

sudo -u deploy npm ci --only=production
sudo -u deploy npm run build

For Python:

sudo -u deploy /home/deploy/venv/bin/pip install -r requirements.txt

4. Process Management with PM2

Create PM2 configuration:

sudo -u deploy nano /home/deploy/apps/your-app/ecosystem.config.js
module.exports = {
  apps: [{
    name: 'your-app',
    script: 'dist/index.js',
    instances: 'max',
    exec_mode: 'cluster',
    env: {
      NODE_ENV: 'production',
      PORT: 3000
    },
    error_file: '/home/deploy/logs/your-app-error.log',
    out_file: '/home/deploy/logs/your-app-out.log',
    log_file: '/home/deploy/logs/your-app.log',
    max_restarts: 3,
    min_uptime: '5s'
  }]
}

Start the application:

# Create logs directory
sudo -u deploy mkdir -p /home/deploy/logs

# Start with PM2
sudo -u deploy pm2 start ecosystem.config.js
sudo -u deploy pm2 save
sudo -u deploy pm2 startup

Monitoring and Maintenance

Basic Monitoring

Install monitoring tools:

apt install -y htop iotop nethogs

Check application logs:

# PM2 logs
sudo -u deploy pm2 logs

# Nginx logs
tail -f /var/log/nginx/access.log
tail -f /var/log/nginx/error.log

# System logs
journalctl -u nginx
journalctl -u postgresql

Automated Backups

Create backup script:

sudo -u deploy nano /home/deploy/scripts/backup.sh
#!/bin/bash
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/home/deploy/backups"

# Create backup directory
mkdir -p $BACKUP_DIR

# Database backup
pg_dump -h localhost -U your_app_user your_app_db > $BACKUP_DIR/db_backup_$DATE.sql

# Application backup
tar -czf $BACKUP_DIR/app_backup_$DATE.tar.gz /home/deploy/apps/your-app

# Keep only last 7 days of backups
find $BACKUP_DIR -type f -mtime +7 -delete

echo "Backup completed: $DATE"

Make executable and add to crontab:

chmod +x /home/deploy/scripts/backup.sh

# Add to crontab (daily backup at 2 AM)
sudo -u deploy crontab -e
# Add: 0 2 * * * /home/deploy/scripts/backup.sh

Performance Optimization

Nginx Optimization

Add to your Nginx configuration:

# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;

# Static file caching
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
}

# Rate limiting
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req zone=api burst=20 nodelay;

System Optimization

# Increase file descriptor limits
echo "* soft nofile 65536" >> /etc/security/limits.conf
echo "* hard nofile 65536" >> /etc/security/limits.conf

# Optimize kernel parameters
echo "net.core.somaxconn = 65536" >> /etc/sysctl.conf
echo "net.ipv4.tcp_max_syn_backlog = 65536" >> /etc/sysctl.conf
sysctl -p

Deployment Automation

For frequent deployments, create a deployment script:

sudo -u deploy nano /home/deploy/scripts/deploy.sh
#!/bin/bash
APP_DIR="/home/deploy/apps/your-app"

cd $APP_DIR

# Pull latest code
git pull origin main

# Install dependencies
npm ci --only=production

# Build application
npm run build

# Restart with zero downtime
pm2 reload ecosystem.config.js

echo "Deployment completed successfully"

This approach has served us well across dozens of client projects. When we deployed the QuickLotz WMS system, this exact Hetzner VPS setup handled thousands of concurrent warehouse operations without breaking a sweat.

Key Takeaways

Security first: Disable root login, use SSH keys, configure firewall and fail2ban before anything else • Use reverse proxy: Nginx handles SSL termination, static files, and provides better security than direct application exposure • Process management: PM2 or systemd ensures your application stays running and restarts automatically • Monitor everything: Set up logging, monitoring, and automated backups from day one • Automate deployments: Create scripts for consistent, repeatable deployments • Performance matters: Enable gzip, configure caching, and optimize system parameters for production loads • Plan for scale: Start with appropriate server size but design your setup to scale horizontally when needed

When to Choose Hetzner vs Alternatives

Based on our experience deploying various systems, choose Hetzner when:

  • You need predictable, low-cost hosting
  • Your application doesn’t require extensive managed services
  • You’re comfortable with infrastructure management
  • European data sovereignty matters
  • You want excellent price-to-performance ratios

Consider alternatives like AWS when you need extensive managed services or have complex compliance requirements. We covered this decision matrix in detail in our VPS vs AWS comparison.

This Hetzner VPS setup web application hosting approach has proven reliable across our client projects, from simple APIs to complex enterprise systems. The combination of cost efficiency, performance, and control makes it an excellent choice for most web applications.

If you’re building a web application and need help with deployment strategy, infrastructure setup, or scaling decisions, we’d love to help. Reach out to discuss your project.

More from the blog

Need reliable infrastructure?

We build CI/CD pipelines, monitoring systems, and deployment infrastructure engineered for reliability.

Get our AI implementation playbook

A practical guide to evaluating, planning, and deploying AI in your business. Free, no spam.