tva
← Insights

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

  1. Log in to the Hetzner Cloud Console
  2. Create a new project
  3. 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:

  1. Enable 2-factor authentication
  2. Generate an App Password
  3. 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:

  1. Go to your domain registrar/DNS provider
  2. 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 that N8N_PROXY_HOPS=1 is 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