Loading tutorials…
Loading tutorials…
Self-hosting n8n is cheap until the day you upgrade and lose every workflow because the docs let you start on SQLite. This walks through the durable install path — Docker + Postgres + HTTPS — that survives version bumps and restarts.
Who this is forTechnical founders or ops leads choosing self-hosted n8n over Cloud to avoid per-execution pricing or to keep data on their own infrastructure. If you have a small VPS, basic Docker familiarity, and a domain, this gets you to a production install in 3-5 hours.
What you'll need
Step 1
The default n8n install uses SQLite. It works until your first major upgrade or container restart with a misconfigured volume — at which point you can lose everything. Start with Postgres.
n8n stores workflows, executions, credentials, and history in a database. The Docker quickstart uses SQLite by default because it has zero setup. That convenience costs you the first time something goes wrong.
SQLite + Docker means one file on a single volume. Volume misconfigurations are the #1 cause of self-hosted data loss. They are also irreversible.
Use Postgres from day one. The marginal effort is one extra container in your Compose file and three more environment variables. The payoff is a backing store you can backup, replicate, and trust through upgrades.
Recommended: Postgres 16 in a separate container in the same Compose file, with its own named volume.
Step 2
Two services: postgres + n8n. Named volumes for each. Environment variables for credentials encryption, webhook URL, and timezone.
Create `~/n8n/docker-compose.yml` on the VPS. Define two services.
Service 1: `postgres` using `postgres:16-alpine`, with `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB` env vars, and a named volume mounted at `/var/lib/postgresql/data`.
Service 2: `n8n` using `n8nio/n8n:latest`, depending_on postgres, with these env vars: `DB_TYPE=postgresdb`, the four `DB_POSTGRESDB_*` connection vars, `N8N_HOST=automations.yourdomain.com`, `N8N_PROTOCOL=https`, `WEBHOOK_URL=https://automations.yourdomain.com/`, `GENERIC_TIMEZONE=America/New_York`, and CRITICALLY `N8N_ENCRYPTION_KEY=<32+ random characters>`.
Mount a named volume at `/home/node/.n8n` for n8n config. Map host port 5678 to container 5678 (the reverse proxy will sit in front).
Generate the encryption key with `openssl rand -hex 32` and store it somewhere safe. If you lose it, every saved credential becomes unrecoverable.
Step 3
Put Caddy or Nginx in front of n8n. Caddy is one config block and handles Let's Encrypt automatically. Webhooks require real HTTPS.
n8n must be served over HTTPS for OAuth flows, secure webhooks, and most third-party integrations to work. Self-signed certs cause silent failures.
Easiest path: Caddy. Install with `apt install caddy`. Create `/etc/caddy/Caddyfile` with one block: `automations.yourdomain.com { reverse_proxy localhost:5678 }`. Restart Caddy. SSL provisions automatically in 30-60 seconds.
Verify HTTPS works: open `https://automations.yourdomain.com` in a browser. The n8n login screen should load on the first try with no certificate warnings.
If you prefer Nginx, the config is longer but the outcome is the same — proxy_pass to localhost:5678 with WebSocket upgrade headers (n8n needs them for the editor).
Step 4
Run `docker compose up -d`. Visit the HTTPS URL. Create the owner account. Set up SMTP for password resets and execution alerts.
From the `~/n8n` directory: `docker compose up -d`. Wait 30-60 seconds for Postgres to initialize and n8n to start.
Visit `https://automations.yourdomain.com`. The owner-setup page appears on the first load. Set an email and a strong password — this is your admin account.
After setup, go to Settings → SMTP. Add real SMTP credentials (SendGrid, AWS SES, or Postmark). Without this, password resets and error workflow emails will not send.
Go to Settings → Users and invite teammates. Each user gets their own account with role-based access (Owner, Admin, Editor, Viewer).
Step 5
Pin a version tag, schedule weekly Postgres dumps, and test the restore path before you need it.
Change the n8n image from `n8nio/n8n:latest` to `n8nio/n8n:1.X.Y` — the latest tag will eventually pull a version with breaking changes during a routine restart and surprise you.
Schedule a daily Postgres dump via cron: `docker exec n8n_postgres_1 pg_dump -U n8n n8n | gzip > /backups/n8n-$(date +%F).sql.gz`. Keep 30 days.
Test the restore path quarterly. Provision a throwaway VPS, restore the latest dump, point a new n8n container at it, confirm workflows + credentials load. If this test fails, your backups are not backups.
For upgrades: read the n8n changelog, bump the version tag, `docker compose pull && docker compose up -d`, watch logs for migration errors. Postgres migrations are usually instant but very rare 0.x → 1.x bumps need manual steps.
Step 6
Build a 2-node workflow (Webhook → HTTP Request). Trigger from outside the network. Verify execution succeeds and persists across a container restart.
In the n8n editor: Create a new workflow. Add a Webhook trigger node. Set HTTP Method to POST, copy the production URL it generates.
Add an HTTP Request node downstream — POST to a service like webhook.site so you can see the relayed payload.
Save and activate the workflow. From your laptop terminal (or any machine NOT on the VPS), `curl -X POST <webhook-url> -d '{"test":1}'`. Confirm the execution appears under Executions → Successful within 5 seconds.
Run `docker compose restart n8n`. After ~60s, repeat the curl. The execution should succeed and appear in the Executions list — which proves Postgres persistence works.
Common mistakes
Starting with SQLite "just to try it"
What goes wrong: Six weeks in, a routine `docker compose down -v` (run because Stack Overflow said to) wipes the volume and you lose every workflow + credential. Recovery is impossible. Rebuilding 30 workflows costs 40-80 hours at $100/hr operator time — $4,000-8,000 of avoidable work.
How to avoid: Postgres from day one. Two extra environment variables and one more container. There is no scenario where SQLite is the right call for production self-hosted n8n.
Missing or rotating the N8N_ENCRYPTION_KEY
What goes wrong: Every saved credential (Gmail OAuth, HubSpot API key, Stripe secret) is encrypted with this key. Lose it or rotate it and every credential in the database becomes garbage. You will reconnect every OAuth account by hand. That is 2-6 hours of work and silent breakage of every running workflow.
How to avoid: Generate the key once with `openssl rand -hex 32`. Store it in your password manager AND a sealed backup. Never rotate it without first re-saving every credential.
Mismatched WEBHOOK_URL and reverse proxy domain
What goes wrong: Every webhook node returns a URL that does not match what is publicly reachable. Stripe webhooks fail signature validation. Typeform webhooks return 404. You spend a day blaming the third-party service before realizing the env var is wrong.
How to avoid: Set WEBHOOK_URL to the exact public HTTPS URL including trailing slash: `WEBHOOK_URL=https://automations.yourdomain.com/`. Restart the n8n container after changing.
Using `:latest` as the image tag
What goes wrong: A scheduled VPS reboot or `docker compose pull` pulls a new major version with breaking schema migrations. n8n fails to start. You scramble to pin the version, often during a workday outage. ~2-4 hours of downtime + the panic tax.
How to avoid: Pin the version tag explicitly: `n8nio/n8n:1.62.4` (or current stable). Bump deliberately after reading the changelog.
No backup test before production scale
What goes wrong: You schedule daily pg_dump backups. Three months in, a Postgres volume corrupts. You try to restore — and the dumps are missing the schema, or stored in a format your replacement Postgres cannot read. The backups exist but are useless. Total loss.
How to avoid: Schedule a quarterly restore test on a throwaway VPS. Restore the latest dump. Start n8n against it. Confirm workflows + credentials + executions appear. Mark the date completed.
Running on 1GB RAM and ignoring OOM kills
What goes wrong: n8n with the editor open + a few workflows easily uses 800MB-1.2GB. On a 1GB VPS, Postgres and n8n compete for memory; the kernel OOM-kills processes randomly. Workflows fail silently. Executions disappear mid-run.
How to avoid: Start at 2GB RAM minimum, 4GB recommended once you have 20+ workflows. Add swap as a safety net (1-2GB swap on `/swapfile`). Monitor via `docker stats` weekly.
Recap
Done — what's next
How to set up an n8n Cloud account
Read the next tutorial
Hand it off
Self-hosting n8n is cheap on paper and expensive on attention. Most teams that go self-hosted end up paying a specialist $400-800/mo to actually run it — Postgres backups, version bumps, SSL renewal monitoring, OOM tuning. EverestX automation specialists at $14-16/hr will provision the stack AND own ongoing operations.
See specialist rates
Self-host if you have engineering capacity, want to control data residency, or your workflow volume makes Cloud's per-execution pricing untenable (typically 50K+ executions/month). Cloud if you do not have a DevOps person and value not running Postgres yourself. Most marketing-led teams should pick Cloud.
Yes. Export workflows individually as JSON or use the bulk export. Credentials cannot be exported (they are encrypted with Cloud's key) and must be re-entered on self-hosted. Plan a 1-2 day migration window for a 20-30 workflow stack.
Floor: 1 vCPU / 2GB RAM for <10 workflows with low volume. Reasonable: 2 vCPU / 4GB RAM with 1GB swap for up to 50 workflows. Heavy: 4 vCPU / 8GB RAM + Postgres on a separate VPS for 100+ workflows or queue-mode setups.
Only if you run high-throughput workflows (5+ concurrent executions sustained) or want to scale workers horizontally. For most teams, the default single-process mode is fine until 100+ executions/minute.
If you use Caddy, it auto-renews via Let's Encrypt — set it and forget it. If you use Nginx with certbot, schedule `certbot renew` weekly via cron. Monitor with a UptimeRobot or BetterStack HTTPS check that pages you 7 days before expiry.
n8n
Self-hosted n8n is great if you like managing Postgres. n8n Cloud is great if you like building workflows. This walks through the Cloud setup path — plan choice, environments, users, first workflow — in under an hour.
n8n
You have n8n running. Now build one real workflow that does something useful. This walks a real trigger → action chain end-to-end with the expression-mapping detail most tutorials skip.
n8n
Credentials are the hidden failure point of every n8n stack. They expire, get rotated, get shared too broadly, or get lost when a teammate leaves. This walks the disciplined pattern for managing them.
n8n
DIY n8n is great until you have 15 workflows and a credentials audit you keep deferring. This is the honest framework: when the cost of self-managing exceeds the cost of a specialist, and how to tell which side you are on.
Zapier
You signed up for Zapier and the dashboard is staring at you. This walks through one real, working Zap end-to-end — trigger app, action app, sample data, test, turn on — without the marketing fluff.