We've self-hosted n8n for dozens of clients over the past two years. The cloud version is fine for testing, but once you hit real workflow volume, the pricing jumps fast. Self-hosting puts you in control: unlimited workflows, unlimited executions, your data stays on your server.
This guide covers the exact setup we use in production — not a "hello world" demo. By the end you'll have n8n running on a proper domain with HTTPS, auto-restart, and basic security hardening.
What You Need Before You Start
- A VPS with at least 1GB RAM (2GB recommended) — DigitalOcean, Hetzner, or Vultr all work
- A domain name with DNS access
- Ubuntu 22.04 LTS (what this guide uses)
- Basic comfort with SSH and the command line
We typically use Hetzner CX21 (2 vCPU, 4GB RAM, €4.15/mo) for client setups — it's overkill for most workflows but leaves headroom for growth. DigitalOcean's $6/mo droplet works fine for lighter use.
Step 1: Install Docker & Docker Compose
SSH into your server and run the following:
sudo apt update && sudo apt upgrade -y
# Install Docker
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
newgrp docker
# Verify
docker --version
Docker Compose V2 is included with modern Docker installs. Confirm with docker compose version. If it's missing, install the plugin: sudo apt install docker-compose-plugin
Step 2: Point Your Domain to the Server
Before setting up HTTPS, your domain needs to resolve to your server IP. In your DNS provider, add an A record:
Name: n8n (or @ for root domain)
Value: YOUR.SERVER.IP.ADDRESS
TTL: 300
Wait 5–10 minutes for DNS to propagate. Confirm with dig n8n.yourdomain.com +short — it should return your server IP.
Step 3: Create the Docker Compose File
Create a directory for your n8n stack and add the compose file:
nano docker-compose.yml
Paste this configuration:
services:
n8n:
image: n8nio/n8n:latest
restart: always
ports:
- "5678:5678"
environment:
- N8N_HOST=n8n.yourdomain.com
- N8N_PORT=5678
- N8N_PROTOCOL=https
- NODE_ENV=production
- WEBHOOK_URL=https://n8n.yourdomain.com/
- GENERIC_TIMEZONE=Europe/London
- N8N_BASIC_AUTH_ACTIVE=true
- N8N_BASIC_AUTH_USER=admin
- N8N_BASIC_AUTH_PASSWORD=changeme123
volumes:
- n8n_data:/home/node/.n8n
volumes:
n8n_data:
Important: Change N8N_BASIC_AUTH_PASSWORD to something secure. This is your login password.
Step 4: Set Up Caddy for HTTPS
Caddy handles HTTPS automatically — no certbot needed. Install it:
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy
Create your Caddyfile:
reverse_proxy localhost:5678
}
That's it. Caddy fetches a Let's Encrypt certificate automatically on first request. Reload Caddy:
Step 5: Start n8n
docker compose up -d
Wait 30 seconds, then visit https://n8n.yourdomain.com. You should see the n8n login screen. Log in with the credentials you set above.
- n8n running on your own VPS
- HTTPS with auto-renewing Let's Encrypt cert
- Basic auth protecting the UI
- Auto-restart on server reboot
- Persistent data storage via Docker volume
Production Hardening (Don't Skip This)
The basic setup above works, but for anything you're relying on daily, add these:
- UFW firewall:
sudo ufw allow 22,80,443/tcp && sudo ufw enable - Fail2ban: protects SSH from brute-force —
sudo apt install fail2ban - Automatic backups: set up a cron job to dump the Docker volume to S3 or Backblaze B2 weekly
- Pin the n8n version: replace
n8nio/n8n:latestwith a specific version (e.g.n8nio/n8n:1.40.0) to prevent surprise breaking changes - Enable 2FA: n8n has built-in TOTP support in settings — enable it
Adding a PostgreSQL Database
By default n8n uses SQLite — fine for small use, but under heavy load (1000+ executions/day) you'll want PostgreSQL. Update your compose file:
postgres:
image: postgres:15
restart: always
environment:
- POSTGRES_USER=n8n
- POSTGRES_PASSWORD=strongpassword
- POSTGRES_DB=n8n
volumes:
- postgres_data:/var/lib/postgresql/data
n8n:
# ... existing config ...
environment:
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_USER=n8n
- DB_POSTGRESDB_PASSWORD=strongpassword
- DB_POSTGRESDB_DATABASE=n8n
depends_on:
- postgres
Updating n8n
Keeping n8n updated is two commands:
docker compose pull
docker compose up -d
The old container is replaced with zero workflow data loss (it's stored in the volume). We run updates on client servers monthly — check the n8n changelog first for breaking changes.
Monitoring & Alerts
n8n has built-in workflow execution logging under Settings → Log Streaming. For server-level monitoring, we use Uptime Kuma — another Docker container that pings your n8n URL and sends Telegram/Slack alerts if it goes down. Free, self-hosted, five minutes to set up.
Cost Comparison: Self-Host vs Cloud
| Option | Monthly Cost | Executions | Workflows |
|---|---|---|---|
| n8n Cloud Starter | $24 | 2,500/mo | 5 active |
| n8n Cloud Pro | $60 | 10,000/mo | 15 active |
| Self-hosted (Hetzner CX21) | ~$5 | Unlimited | Unlimited |
For our agency clients, self-hosting typically pays for itself within the first month. The only real cost is your time setting it up — which is why this guide exists.
When to Use Cloud Instead
Self-hosting isn't always the right call. Stick with n8n cloud if:
- You're not comfortable managing a Linux server
- You need n8n's enterprise features (SSO, audit logs)
- You want someone else to handle updates and uptime
- Your workflow volume is low (<500 executions/month)
- VPS with 2GB+ RAM on Ubuntu 22.04
- Docker + Docker Compose installed
- Domain A record pointed at server IP
- docker-compose.yml with n8n config
- Caddy installed and configured as reverse proxy
- UFW firewall + Fail2ban enabled
- PostgreSQL added for production load
- Uptime Kuma monitoring running
Questions or hitting a wall? We help teams migrate from n8n cloud to self-hosted as part of our automation builds. Get in touch if you want it done for you.