Complete Hetzner VPS Setup for Web Application Hosting: Production-Ready Guide
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.
Check your inbox.
Something went wrong. Please try again.