Self-Hosting n8n on Hetzner Cloud: Complete Docker Setup Tutorial
Workflow automation has become essential for modern businesses, but SaaS solutions can become expensive as teams grow. We’ll show you how to set up your own n8n instance on Hetzner Cloud for about €6/month plus domain costs (as of October 2026), giving you unlimited workflows and complete control over your automation platform.
What You’ll Build
By the end of this tutorial, you’ll have:
- ✅ Fully functional n8n installation with HTTPS
- ✅ Automatic SSL certificates via Let’s Encrypt
- ✅ Email integration for user accounts
- ✅ Automatic startup after server restarts
- ✅ A solid base for professional use that you can harden further for production
Monthly cost: €5.99 (CX23 server, excl. VAT, incl. a primary IPv4 address, as of October 2026) + domain costs
Prerequisites
- Domain name pointing to your server IP
- Basic command-line knowledge
- Hetzner Cloud account
Step 1: Server Setup
Create a Hetzner Cloud Server
- Log in to the Hetzner Cloud Console
- Create a new project
- Add a server with these specifications:
- Image: Ubuntu 24.04 LTS
- Type: CX23 (2 vCPU, 4 GB RAM, 40 GB disk); Hetzner retired the CX11 for new orders in September 2024, and availability of the cost-optimized plans varies by location
- Location: Choose the location closest to your users
- SSH Key: Add your public key
Initial Server Configuration
Connect to your server:
ssh root@YOUR_SERVER_IP
Update the system:
apt update && apt upgrade -y
Step 2: Install Docker
Install Docker and Docker Compose:
# Install dependencies
apt install apt-transport-https ca-certificates curl software-properties-common gnupg -y
# Add Docker GPG key
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
# Add Docker repository
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
# Install Docker
apt update
apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y
# Create Docker network for our services
docker network create proxy
Verify Docker installation:
docker --version
docker compose version
Step 3: Configure Traefik Reverse Proxy
Create directory structure:
mkdir -p /opt/traefik/data
cd /opt/traefik
Create Traefik configuration file:
nano /opt/traefik/data/traefik.yml
Add this configuration:
api:
dashboard: false
entryPoints:
http:
address: ":80"
http:
redirections:
entryPoint:
to: https
scheme: https
https:
address: ":443"
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
certificatesResolvers:
letsencrypt:
acme:
email: [email protected] # Change this!
storage: acme.json
httpChallenge:
entryPoint: http
Create acme.json file for SSL certificates:
touch /opt/traefik/data/acme.json
chmod 600 /opt/traefik/data/acme.json
Create Traefik Docker Compose file:
nano /opt/traefik/docker-compose.yml
Add this configuration:
version: '3'
services:
traefik:
image: traefik:v3.4
container_name: traefik
restart: always
ports:
- "80:80"
- "443:443"
networks:
- proxy
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./data/traefik.yml:/etc/traefik/traefik.yml
- ./data/acme.json:/acme.json
labels:
- "traefik.enable=true"
networks:
proxy:
external: true
Step 4: Set Up SMTP Relay (Optional but Recommended)
Create mailserver directory:
mkdir -p /opt/mailserver
cd /opt/mailserver
Create Docker Compose file:
nano /opt/mailserver/docker-compose.yml
Add this configuration:
version: '3'
services:
mailserver:
image: bytemark/smtp
restart: always
networks:
- proxy
environment:
- RELAY_HOST=smtp.gmail.com # Change to your SMTP provider
- RELAY_PORT=587
- [email protected] # Change this
- RELAY_PASSWORD=your-app-password # Change this
- MAILNAME=mail.yourdomain.com # Change this
networks:
proxy:
external: true
Note: n8n reaches the relay over the shared proxy network, so the relay publishes no port. Docker-published ports bypass UFW rules, so only add one if another host must send through the relay. MAILNAME sets the relay’s mail hostname. For Gmail, you’ll need to:
- Enable 2-factor authentication
- Generate an App Password
- Use the App Password (not your regular password)
Step 5: Configure n8n
Create the n8n directory. The n8n container runs as the node user (UID 1000), so make sure the bind-mounted data folder is writable by that user, for example with mkdir -p /opt/n8n/data && chown -R 1000:1000 /opt/n8n/data:
mkdir -p /opt/n8n
cd /opt/n8n
Create Docker Compose file:
nano /opt/n8n/docker-compose.yml
Add this configuration:
version: '3'
services:
n8n:
image: n8nio/n8n:1.95.2
restart: always
environment:
- N8N_HOST=n8n.yourdomain.com # Change this!
- NODE_ENV=production
- N8N_PROTOCOL=https
- N8N_PORT=5678
- N8N_EDITOR_BASE_URL=https://n8n.yourdomain.com # Change this!
# Email configuration
- N8N_EMAIL_MODE=smtp
- N8N_SMTP_HOST=mailserver
- N8N_SMTP_PORT=25
- N8N_SMTP_SSL=false
- N8N_SMTP_USER=
- N8N_SMTP_PASS=
- [email protected] # Change this!
# Important fixes for proxy setup
- N8N_PROXY_HOPS=1
- N8N_RUNNERS_ENABLED=true
volumes:
- ./data:/home/node/.n8n
networks:
- proxy
labels:
- "traefik.enable=true"
- "traefik.http.routers.n8n.rule=Host(`n8n.yourdomain.com`)" # Change this!
- "traefik.http.routers.n8n.entrypoints=https"
- "traefik.http.routers.n8n.tls.certresolver=letsencrypt"
- "traefik.http.services.n8n.loadbalancer.server.port=5678"
networks:
proxy:
external: true
Step 6: Create Systemd Services for Auto-Start
Traefik Service
nano /etc/systemd/system/traefik-docker.service
Add this configuration:
[Unit]
Description=Traefik Docker Compose
Requires=docker.service
After=docker.service
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/traefik
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
[Install]
WantedBy=multi-user.target
Mailserver Service
nano /etc/systemd/system/mailserver-docker.service
Add this configuration:
[Unit]
Description=Mailserver Docker Compose
Requires=docker.service
After=docker.service
Before=n8n-docker.service
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/mailserver
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
[Install]
WantedBy=multi-user.target
n8n Service
nano /etc/systemd/system/n8n-docker.service
Add this configuration:
[Unit]
Description=n8n Docker Compose
Requires=docker.service
After=docker.service traefik-docker.service mailserver-docker.service
Wants=network-online.target
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/n8n
ExecStartPre=/bin/bash -c 'until docker container inspect traefik > /dev/null 2>&1; do sleep 2; done'
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
Restart=on-failure
RestartSec=30
TimeoutStartSec=180
[Install]
WantedBy=multi-user.target
Enable and Start Services
# Reload systemd daemon
systemctl daemon-reload
# Enable services to start on boot
systemctl enable traefik-docker.service
systemctl enable mailserver-docker.service
systemctl enable n8n-docker.service
# Start services
systemctl start traefik-docker.service
sleep 10
systemctl start mailserver-docker.service
sleep 10
systemctl start n8n-docker.service
Step 7: DNS Configuration
Point your domain to your server:
- Go to your domain registrar/DNS provider
- Create an A record:
- Name: n8n
- Type: A
- Value: YOUR_SERVER_IP
- TTL: 300 (or minimum allowed)
Wait for DNS propagation (can take up to 24 hours, usually much faster).
Step 8: Test Your Installation
Check if services are running:
docker ps
You should see three containers running: traefik, mailserver, and n8n.
Visit your n8n instance:
https://n8n.yourdomain.com
You should see the n8n setup page over HTTPS, with your browser showing a valid certificate.
Troubleshooting Common Issues
SSL Certificate Not Working
Check Traefik logs:
docker logs traefik
Common causes:
- DNS not pointing to server
- Firewall blocking ports 80/443
- Wrong domain in configuration
n8n Not Accessible
Check n8n logs:
docker logs n8n-n8n-1
Common fixes:
- Verify that the domain configuration matches across all files
- Check whether all containers are on the same network
- If logs show
ERR_ERL_UNEXPECTED_X_FORWARDED_FOR, check thatN8N_PROXY_HOPS=1is set as in the configuration above; n8n documents it for running behind a reverse proxy
Email Not Working
Check mailserver logs:
docker logs mailserver-mailserver-1
Verify SMTP credentials and settings.
Services Not Starting After Reboot
Check service status:
systemctl status traefik-docker.service
systemctl status n8n-docker.service
Restart if needed:
systemctl restart traefik-docker.service
systemctl restart n8n-docker.service
Maintenance and Updates
Regular Maintenance
Weekly checks:
# Check container status
docker ps
# Check disk space
df -h
# Check logs for errors
docker logs n8n-n8n-1 | grep -i error
Monthly updates:
# Update container images
cd /opt/n8n && docker compose pull && docker compose up -d
cd /opt/traefik && docker compose pull && docker compose up -d
cd /opt/mailserver && docker compose pull && docker compose up -d
# Clean up old images
docker system prune -f
Back Up Your Data
Create a backup script. The default SQLite database is copied while n8n is running, so stop n8n briefly or run the backup during a quiet period to avoid an inconsistent copy, and test a restore before relying on it:
nano /opt/backup-n8n.sh
Add this content:
#!/bin/bash
mkdir -p /opt/backups
tar -czf /opt/backups/n8n-backup-$(date +%Y%m%d).tar.gz /opt/n8n/data
find /opt/backups -name "n8n-backup-*.tar.gz" -mtime +30 -delete
Make it executable and add to cron:
chmod +x /opt/backup-n8n.sh
# Add to crontab (daily backup at 2 AM)
crontab -e
# Add this line:
0 2 * * * /opt/backup-n8n.sh
Security Hardening
Configure Firewall
# Install UFW
apt install ufw
# Set defaults
ufw default deny incoming
ufw default allow outgoing
# Allow SSH
ufw allow 22
# Allow HTTP/HTTPS
ufw allow 80
ufw allow 443
# Enable firewall
ufw enable
Secure SSH
Edit SSH configuration:
nano /etc/ssh/sshd_config
Make these changes:
PermitRootLogin prohibit-password
PasswordAuthentication no
PubkeyAuthentication yes
Restart SSH:
systemctl restart ssh
Set Up Fail2Ban
apt install fail2ban
# Copy default configuration
cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
# Enable and start
systemctl enable fail2ban
systemctl start fail2ban
Cost Breakdown
Monthly costs:
- Hetzner CX23 server: €5.99 (excl. VAT, incl. a primary IPv4 address, as of October 2026)
- Domain (yearly, varies): ~€10-15/year
- Total: ~€7/month
Compare to n8n Cloud:
- n8n Starter: €20/month billed annually (2,500 executions, as of October 2026)
- n8n Pro: €50/month billed annually (10,000 executions)
- Savings: about €150-520/year
Scaling Your Setup
Multiple n8n Instances
You can run multiple isolated n8n instances for different teams:
Create /opt/n8n-team2/docker-compose.yml:
version: '3'
services:
n8n-team2:
image: n8nio/n8n:1.95.2
restart: always
environment:
- N8N_HOST=team2.yourdomain.com
# ... rest of configuration
volumes:
- ./data:/home/node/.n8n
networks:
- proxy
labels:
- "traefik.enable=true"
- "traefik.http.routers.n8n-team2.rule=Host(`team2.yourdomain.com`)"
- "traefik.http.routers.n8n-team2.entrypoints=https"
- "traefik.http.routers.n8n-team2.tls.certresolver=letsencrypt"
- "traefik.http.services.n8n-team2.loadbalancer.server.port=5678"
networks:
proxy:
external: true
Upgrade Server Resources
If you need more power, upgrade your Hetzner server:
- CX33: 4 vCPU, 8 GB RAM (€8.99/month excl. VAT, incl. a primary IPv4 address, as of October 2026)
- CX43: 8 vCPU, 16 GB RAM (€16.49/month excl. VAT, incl. a primary IPv4 address)
Conclusion
You now have a working n8n installation that costs a fraction of hosted solutions while giving you complete control. The setup includes automatic HTTPS, email functionality, and robust service management.
Key benefits of this approach:
- Cost-effective: Save hundreds of euros annually compared with n8n Cloud
- Full control: No execution quotas; limits come from your server resources and n8n’s Sustainable Use License
- Privacy: Workflow and execution data stay on your server, except data your workflows send to external services
- Customizable: Modify and extend as needed
We have run this configuration in production. The n8n and Traefik examples pin image versions; to update, raise those tags in each docker-compose.yml before running the update commands. For business-critical use, test your backup restores and add monitoring.
About tva
tva ensures comprehensive infrastructure management of database systems, cloud environments, and global supply chains. Our methodical approach combines rigorous security protocols with performance optimization, while strategic advisory services enable precise coordination of both digital capabilities and physical assets – maintaining the highest standards of operational excellence and compliance throughout all engagements.
Visit tva.sg for more information about our services.
Related Insights
Further Reading
Self-Hosting Windmill on Ubuntu: Complete Docker Setup Tutorial with PostgreSQL Troubleshooting
Hetzner Cloud in Late 2026: Plans and Prices for Self-Hosting n8n, Windmill, Twenty and Ollama
Complete n8n Self-Hosted Troubleshooting Guide 2025: Fixing Execution Data Size & Webhook Problems with Traefik