David Lewin David Lewin
AI Cloud GPUs & Agents

Self-hosted n8n on a $10 VPS: setup, security and real costs vs. Make.com and Zapier

Disclosure: some links in this guide are affiliate links. If you sign up through them, I may earn a commission at no extra cost to you. How this site makes money.

Short answer

Run n8n with Docker Compose on a Linux VPS (2 GB RAM is enough for light use), backed by Postgres and served over HTTPS through Caddy. Back up the database and the encryption key, keep the editor behind login, and note that the license allows internal business use, not reselling n8n as a hosted service.

Make.com and Zapier charge per operation or task. That's great at low volume and painful once a workflow runs thousands of times a day. Self-hosted n8n swaps the per-run bill for a fixed server cost and a bit of maintenance. This guide covers a production-sane setup, not just docker run on a laptop: a real database, HTTPS, backups, and the security basics people skip.

Is self-hosting right for you?

  • Yes if you run high-volume workflows (order syncing, inventory checks, lead routing), need to process data on your own infrastructure, or want to call a local AI model.
  • Probably not if you have a handful of workflows, no one comfortable with a Linux shell, or you need guaranteed uptime with no on-call. n8n Cloud or Make may cost less than your time.
  • License check: n8n is distributed under the Sustainable Use License. Using it for your own or your company's internal workflows is fine. Offering n8n itself to others as a paid hosted service isn't. Read the license if you build automations for clients.

What you need

  • A Linux VPS with 1–2 vCPU and 2 GB RAM for light use, or 4 GB+ if you run many concurrent executions. Vultr, DigitalOcean and Hetzner all work.
  • A domain or subdomain (for example automations.yourbrand.com) with an A record pointing to the server.
  • Docker and the Docker Compose plugin installed.

Step 1: the Docker Compose file

Create a folder such as /opt/n8n containing these three files.

.env (generate the secrets with openssl rand -hex 32):

N8N_DOMAIN=automations.yourbrand.com
POSTGRES_PASSWORD=change-me-long-random
N8N_ENCRYPTION_KEY=change-me-long-random-and-back-it-up

docker-compose.yml:

services:
  postgres:
    image: postgres:16
    restart: unless-stopped
    environment:
      POSTGRES_USER: n8n
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: n8n
    volumes:
      - pgdata:/var/lib/postgresql/data

  n8n:
    image: docker.n8n.io/n8nio/n8n
    restart: unless-stopped
    depends_on: [postgres]
    environment:
      DB_TYPE: postgresdb
      DB_POSTGRESDB_HOST: postgres
      DB_POSTGRESDB_DATABASE: n8n
      DB_POSTGRESDB_USER: n8n
      DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD}
      N8N_HOST: ${N8N_DOMAIN}
      N8N_PROTOCOL: https
      WEBHOOK_URL: https://${N8N_DOMAIN}/
      N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
      GENERIC_TIMEZONE: America/New_York
    volumes:
      - n8n_data:/home/node/.n8n

  caddy:
    image: caddy:2
    restart: unless-stopped
    ports: ["80:80", "443:443"]
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data

volumes:
  pgdata:
  n8n_data:
  caddy_data:

Caddyfile (Caddy gets and renews the HTTPS certificate automatically):

automations.yourbrand.com {
    reverse_proxy n8n:5678
}

Start it with docker compose up -d, open https://automations.yourbrand.com, and create the owner account straight away. Until you do, anyone who finds the URL can.

Step 2: security basics

  • Firewall: allow only 22, 80 and 443 (ufw allow OpenSSH; ufw allow 80; ufw allow 443; ufw enable). n8n's port 5678 and Postgres are never exposed. Only Caddy talks to n8n, over the internal Docker network.
  • Turn on two-factor authentication for every n8n user.
  • Protect webhooks: use the Webhook node's authentication options (header auth or basic auth), and validate signatures from services that send them (Shopify, Stripe).
  • Credentials are encrypted with N8N_ENCRYPTION_KEY. Lose that key and every stored credential is unreadable, even with a full database backup.
  • Update regularly: docker compose pull && docker compose up -d. Read the release notes for breaking changes first.

Step 3: backups that actually restore

# nightly cron on the host (e.g. /etc/cron.d/n8n-backup)
0 3 * * * root cd /opt/n8n && docker compose exec -T postgres pg_dump -U n8n n8n | gzip > /opt/backups/n8n-$(date +\%F).sql.gz
  • Copy backups off the server (object storage or another provider), and store the .env file, with its encryption key, somewhere safe and separate.
  • Test a restore on a fresh server once. A backup you've never restored is a hope, not a backup.
  • Export important workflows to Git as JSON as well. It's cheap version history.

Step 4: connect a local AI model

If you run Ollama (see deploying Ollama or vLLM) on the same server or private network, n8n's AI nodes can use it without data leaving your infrastructure:

  • Same server, Ollama in the same Compose project: base URL http://ollama:11434.
  • Ollama on the host machine: use the host's Docker bridge address, or run Ollama in Compose too. Don't expose 11434 publicly to make this work.

A small CPU-only VPS can run very small models for classification or tagging. For real text generation at volume, use a GPU server or an API.

Step 5: when to scale up

  • Executions start queuing, or the UI gets slow under load: move to a bigger VPS first, since that's the simplest fix.
  • Still not enough: n8n's queue mode (Redis plus separate worker containers) spreads executions across workers.
  • Execution history fills the database: set an execution-data pruning policy so old runs get deleted automatically.

Cost reality check

The server is the cheap part. Budget an hour or two a month for updates, checking failed executions and testing backups. If a workflow failing silently would cost you orders, add an error workflow that alerts you by email or Slack. n8n lets you set one per workflow.

Starter workflow

A good first workflow: Shopify "order created" webhook → check stock with the supplier → write to a Google Sheet → Slack alert if stock is below threshold. It's useful on day one and exercises webhooks, credentials and error handling.

David Lewin
David Lewin

Founder of StealthScale.io and e-commerce infrastructure engineer writing field-tested playbooks on proxies, cloud VPS, anti-detect browsers and SEO.