NodeRecipe.

How to Self-Host n8n: A Complete Beginner's Guide (2026)

Self-host n8n for free with Docker. A step-by-step production setup with Postgres, HTTPS, backups, and fixes for the errors beginners hit most.

By Ali Ilyas · · Updated July 22, 2026

Self-hosting n8n gives you unlimited workflow executions, full ownership of your data, and no per-task fees — all for the cost of a small server (about $5/month). This guide takes you from an empty VPS to a secured, HTTPS-enabled n8n instance running 24/7, using Docker and Docker Compose. You'll also get the environment variables that actually matter, a backup routine, and fixes for the errors that trip up most beginners.

No prior Docker experience is assumed. If you can copy a command into a terminal, you can finish this in about 30 minutes.

What's on this page

Self-hosted vs. n8n Cloud: which do you need?

Before you spin up a server, be honest about which path fits you. n8n offers a paid cloud plan and a free, source-available self-hosted option (the Community Edition). Here's the trade-off:

Self-hosted (Community)n8n Cloud
Price~$5/mo server, unlimited executionsFrom ~$24/mo, metered executions
Data locationYour server, your controln8n's managed infrastructure
MaintenanceYou handle updates & backupsFully managed
Setup time~30 min (this guide)Minutes
Custom nodes / npm packagesFull controlLimited
Best forDevelopers, high-volume, privacy needsNon-technical users, teams who want zero ops

Choose self-hosting if you run a lot of executions, want your data on infrastructure you control, or need custom community nodes. Choose Cloud if you never want to think about servers. The rest of this guide covers self-hosting.

What you'll need

  • A VPS (virtual private server) — roughly $5/month. See the comparison below.
  • SSH access to that server (a terminal on Mac/Linux, or a client like PuTTY/Windows Terminal on Windows).
  • A domain or subdomain you can point at the server (e.g. n8n.yourdomain.com). This is required for HTTPS and for webhooks to work reliably.
  • About 30 minutes.

You do not need to know Docker beforehand — every command is provided.

Where to host n8n (VPS comparison)

Any Linux server with 1 GB of RAM will run a small n8n instance, but 2 GB is more comfortable once you have a few active workflows. These are the popular, budget-friendly options:

ProviderEntry priceRAMBest for
Hetzner Cloud~€4.5/mo2–4 GBBest price-to-performance (EU/US regions)
DigitalOcean$6/mo1 GBBeginner-friendly UI, great docs
RailwayUsage-basedScalesFastest setup, no server management
Home server / Raspberry Pi$0VariesTinkering, LAN-only automations

For most people starting out, Hetzner's CX22 (2 GB RAM) or a DigitalOcean basic droplet is the sweet spot. Pick Ubuntu 24.04 LTS as the operating system when you create the server.

Quick start: run n8n in one command

Want to see n8n before committing to the full setup? SSH into your server and run:

docker volume create n8n_data

docker run -d --restart unless-stopped --name n8n \
  -p 5678:5678 \
  -v n8n_data:/home/node/.n8n \
  docker.n8n.io/n8nio/n8n

Open http://your-server-ip:5678 in your browser and create your owner account. That's a working n8n — but it has no HTTPS, no database, and no domain, so webhooks and OAuth logins will misbehave. Treat this as a test drive, then move to the production setup below.

Don't have Docker yet? Install it in one line: curl -fsSL https://get.docker.com | sh

Production setup with Docker Compose

Docker Compose lets you define n8n, a PostgreSQL database, and a reverse proxy in a single file so they start together and survive reboots. This is the setup you actually want to keep.

Step 1: Install Docker and Compose

On a fresh Ubuntu server, run:

curl -fsSL https://get.docker.com | sh

The Compose plugin ships with modern Docker. Verify both:

docker --version
docker compose version

Step 2: Point your domain at the server

In your DNS provider (Cloudflare, Namecheap, etc.), create an A record for n8n.yourdomain.com pointing to your server's public IP. DNS can take a few minutes to propagate — you can continue while it does.

Step 3: Create the project folder

mkdir -p ~/n8n && cd ~/n8n

Step 4: Create the .env file

This holds your secrets and settings. Create ~/n8n/.env:

# --- Your domain ---
DOMAIN_NAME=n8n.yourdomain.com

# --- Database (Postgres) ---
POSTGRES_USER=n8n
POSTGRES_PASSWORD=change-this-to-a-long-random-string
POSTGRES_DB=n8n

# --- n8n security ---
# Generate with: openssl rand -hex 32
N8N_ENCRYPTION_KEY=paste-a-32-byte-random-hex-string-here
GENERIC_TIMEZONE=America/New_York

Generate strong values before saving:

openssl rand -hex 32   # use for N8N_ENCRYPTION_KEY
openssl rand -hex 16   # use for POSTGRES_PASSWORD

Keep N8N_ENCRYPTION_KEY safe. It encrypts all your saved credentials. If you lose it, every stored credential becomes unreadable and you'll have to re-enter them.

Step 5: Create docker-compose.yml

Create ~/n8n/docker-compose.yml:

services:
  postgres:
    image: postgres:16
    restart: unless-stopped
    environment:
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=${POSTGRES_DB}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 10s
      timeout: 5s
      retries: 5

  n8n:
    image: docker.n8n.io/n8nio/n8n:latest
    restart: unless-stopped
    depends_on:
      postgres:
        condition: service_healthy
    environment:
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_DATABASE=${POSTGRES_DB}
      - DB_POSTGRESDB_USER=${POSTGRES_USER}
      - DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
      - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
      - N8N_HOST=${DOMAIN_NAME}
      - N8N_PROTOCOL=https
      - N8N_PORT=5678
      - WEBHOOK_URL=https://${DOMAIN_NAME}/
      - GENERIC_TIMEZONE=${GENERIC_TIMEZONE}
      - N8N_RUNNERS_ENABLED=true
    volumes:
      - n8n_data:/home/node/.n8n
    expose:
      - 5678

  caddy:
    image: caddy:2
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config
    depends_on:
      - n8n

volumes:
  postgres_data:
  n8n_data:
  caddy_data:
  caddy_config:

Note that n8n uses expose (internal only) rather than publishing port 5678 to the public internet — Caddy is the only service that faces the outside world.

The environment variables that matter

Most self-hosting problems come from missing or wrong environment variables. These are the ones you should always set on a production instance:

VariableWhy it matters
N8N_ENCRYPTION_KEYEncrypts stored credentials. Set it explicitly and back it up.
N8N_HOST / WEBHOOK_URLMust match your real domain, or webhooks and OAuth callbacks break.
N8N_PROTOCOL=httpsTells n8n it's served over HTTPS (behind the proxy).
DB_TYPE=postgresdbUses Postgres instead of the default SQLite — required for reliability at scale.
GENERIC_TIMEZONEMakes Schedule/Cron nodes fire at the times you expect.
N8N_RUNNERS_ENABLED=trueEnables task runners, the recommended way to execute Code nodes.

Adding HTTPS with Caddy

Caddy provisions and renews a free Let's Encrypt certificate automatically — no manual certbot steps. Create ~/n8n/Caddyfile:

n8n.yourdomain.com {
    reverse_proxy n8n:5678
}

Replace n8n.yourdomain.com with your real subdomain (it must match DOMAIN_NAME). That's the entire config — Caddy handles the TLS certificate the first time someone hits the domain.

Now start everything:

cd ~/n8n
docker compose up -d

Give it a minute (the certificate is issued on first request), then open https://n8n.yourdomain.com. Create your owner account and you're live — with a valid HTTPS padlock.

Check that all three containers are healthy:

docker compose ps

Keeping n8n running, updated, and backed up

Auto-restart: restart: unless-stopped in the Compose file means Docker brings your containers back after a crash or server reboot. Nothing else needed.

Updating n8n: pull the newest image and recreate the container. Your data lives in named volumes, so it survives the upgrade:

cd ~/n8n
docker compose pull
docker compose up -d

Backups — do not skip this. Your workflows and credentials live in Postgres and the n8n volume. Back up the database regularly:

docker compose exec -T postgres \
  pg_dump -U n8n n8n > ~/n8n-backup-$(date +%F).sql

Copy that .sql file off the server (to your machine, S3, or a backup service) and keep your .env alongside it — remember, without N8N_ENCRYPTION_KEY the backup's credentials can't be decrypted. Automate it with a daily cron job for peace of mind.

Securing your instance

A public n8n instance is a target. Lock it down:

  • Firewall: allow only ports 22 (SSH), 80, and 443. On Ubuntu: ufw allow 22 && ufw allow 80 && ufw allow 443 && ufw enable.
  • Never expose port 5678 directly. Let Caddy terminate TLS; keep n8n internal (as configured above).
  • Use strong, unique secrets for the database password and encryption key.
  • Keep the server updated: apt update && apt upgrade -y periodically.
  • Restrict SSH to key-based auth and disable root password login.

Common errors and fixes

"This site can't provide a secure connection" / no HTTPS. DNS isn't pointing at your server yet, or port 80/443 is blocked. Confirm the A record resolves (dig n8n.yourdomain.com) and that your firewall/cloud security group allows 80 and 443. Caddy needs port 80 reachable to issue the certificate.

Webhooks return the wrong URL or don't fire. WEBHOOK_URL and N8N_HOST must match your public HTTPS domain exactly. If they still show localhost or an IP, you edited the values but didn't recreate the container — run docker compose up -d again.

"Command 'code' is not allowed" or Code node fails. Enable task runners with N8N_RUNNERS_ENABLED=true (already in the Compose file above) and restart.

Credentials show as "unable to decrypt." The N8N_ENCRYPTION_KEY changed between runs. It must stay identical to the one used when the credentials were saved. Restore the original key from your backup.

Containers keep restarting. Check logs to see which one: docker compose logs n8n --tail=50 or docker compose logs postgres --tail=50. The most common cause is a Postgres password mismatch after editing .env without recreating the database volume.

Schedule/Cron node fires at the wrong time. Set GENERIC_TIMEZONE to your IANA timezone (e.g. Europe/London) and restart.

Frequently asked questions

Is self-hosting n8n free? The n8n Community Edition is free to self-host. Your only cost is the server (~$5/month) and your domain.

Do I need a domain? For a real setup, yes. Webhooks, OAuth logins, and HTTPS all depend on a proper domain. You can test on a raw IP, but production needs a domain.

SQLite or Postgres? n8n defaults to SQLite, which is fine for testing. For anything you rely on, use Postgres (as in this guide) — it handles concurrent executions far more reliably.

How much RAM do I need? 1 GB runs a light instance; 2 GB is comfortable for regular use with several active workflows. Heavy AI or data workflows benefit from more.

How do I move from n8n Cloud to self-hosted? Export your workflows as JSON from Cloud and import them into your self-hosted instance, then re-enter credentials. Your workflows are portable JSON either way.


Ready to build? Grab the ready-made docker-compose.yml and Caddyfile — plus starter workflow templates — on our templates page.