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.
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
.envfile, 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.
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.