n8n self hosted: what changes when you own the box
Running n8n self hosted means the workflow engine, the database and the credentials all live on a server you control, instead of inside n8n’s managed cloud. For a Shopify Plus operation with a dozen live automations (abandoned-cart follow-ups, inventory syncs, support ticket routing), that distinction stops being theoretical the first time a cloud plan’s execution cap gets hit mid-month, or a workflow needs to reach an internal API that a hosted instance can’t route to.
This guide is not for a brand testing its first Zapier-style automation. It’s for a team already running n8n, or committed to running it, that needs the actual setting values — not “configure your environment variables” — to get a self-hosted instance production-ready. If you’re under $3M in revenue and don’t yet have a person accountable for server uptime, n8n Cloud is the right call: the few hours a month this setup saves you in hosting fees will cost more than that in downtime the first time an update breaks something and nobody’s watching.
Prerequisites before you provision anything
Before touching a terminal, decide four things, because each one changes an environment variable you’ll set in step four:
- A domain or subdomain for the instance (e.g.
automate.yourbrand.com). Webhook-triggered workflows (the ones Shopify, Klaviyo and Recharge all use) need a stable, reachable HTTPS URL. - Whether you’re running queue mode from day one. Queue mode needs Redis and at least one worker process; skip it initially if you’re under a few hundred executions a day, and add it later without re-architecting anything else.
- Who owns the backup. Not “which tool”: which person checks, weekly, that a backup actually restores. A backup nobody has tested is a belief, not a backup.
- Whether this replaces an agency-run instance. If you’re migrating off
a managed setup, get the existing
N8N_ENCRYPTION_KEYand a fresh database dump before the handover ends. Don’t wait until after.
Step 1: choose your VPS and size it for the workload
A single n8n instance with Postgres on the same box runs comfortably on 2 vCPUs and 4 GB RAM for most single-brand setups with a dozen or so active workflows. That’s a starting point to size against your own execution volume, not a published minimum from n8n. Watch memory under load during your first busy day (a flash sale, a bulk sync) rather than trusting any fixed number; Postgres and the n8n Node process both grow with concurrent executions, and OOM kills mid-workflow are the most common cause of “why did this automation just stop.”
Pick a provider that gives you root SSH access and a static IP. This is infrastructure you’re operating, not a platform-as-a-service deploy. Any mainstream VPS provider works; the requirement is control, not brand.
Step 2: install Docker and Docker Compose
n8n’s own Docker image is the fastest path to a reproducible setup, and it means an update is a version-string change, not a manual reinstall.
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
Log out and back in so the group membership takes effect, then confirm with
docker --version and docker compose version. Everything from here lives
in a docker-compose.yml file in a directory you’ll want under version
control (even a private git repo), so the exact configuration that’s live
is never only in your head.
Step 3: configure the database — Postgres, not SQLite
n8n defaults to a bundled SQLite database, and that default is the first thing to change. SQLite locks the entire database file on write, so two workflows executing at once queue behind each other rather than running in parallel. This is invisible at low volume and the reason a previously-fine instance suddenly feels slow once you add a fourth or fifth active automation.
Add a Postgres service to your compose file and point n8n at it with these environment variables:
DB_TYPE=postgresdb
DB_POSTGRESDB_HOST=postgres
DB_POSTGRESDB_PORT=5432
DB_POSTGRESDB_DATABASE=n8n
DB_POSTGRESDB_USER=n8n
DB_POSTGRESDB_PASSWORD=<a generated password, not "n8n">
Set this before the first launch. n8n initialises its schema against whichever database it finds on first boot. Switching from SQLite to Postgres afterward means exporting every workflow and credential manually and re-importing them, since there’s no built-in migration path between the two.
Step 4: set the encryption key before first launch
The encryption key decides whether a disaster is a bad afternoon or a
rebuild from scratch. Set N8N_ENCRYPTION_KEY explicitly, as a fixed value
you generate once and store outside the server:
N8N_ENCRYPTION_KEY=<a random 32+ character string>
If you don’t set this, n8n generates one automatically on first boot and
stores it in the container’s local config. It won’t survive a container
rebuild, a volume misconfiguration, or a migration to a new host unless
you specifically preserve that file. Generate your own with
openssl rand -base64 32, write it into a password manager the same day,
and treat it with the same seriousness as a database root password:
functionally, it is one for every credential n8n stores.
Step 5: configure queue mode if you’re past single-worker volume
Below a few hundred executions a day, n8n’s default “regular” mode (one process running workflows as they trigger) is simpler to operate and sufficient. Past that point, or the moment a single long-running workflow (a full product catalogue sync, say) starts blocking shorter ones behind it, queue mode separates triggering from execution:
EXECUTIONS_MODE=queue
QUEUE_BULL_REDIS_HOST=redis
QUEUE_BULL_REDIS_PORT=6379
This needs a Redis service in your compose file and at least one process
running with N8N_DISABLE_UI=true acting purely as a worker, pulling jobs
off the queue. Add workers horizontally as volume grows. This is the actual
benefit of self-hosting over a fixed-tier cloud plan: you scale the
compute, not the invoice line.
Step 6: put a reverse proxy and TLS in front of it
n8n listens on port 5678 by default; don’t expose that port directly to the internet. Run Caddy, Nginx or Cloudflare’s own proxy in front, terminate TLS there, and set:
N8N_PROTOCOL=https
N8N_HOST=automate.yourbrand.com
N8N_PORT=5678
WEBHOOK_URL=https://automate.yourbrand.com/
WEBHOOK_URL matters specifically: it’s the URL n8n hands to Shopify,
Klaviyo or any other service when a workflow registers a webhook. Get it
wrong (an internal Docker hostname, or http instead of https) and
webhook-triggered workflows register successfully but never actually fire.
This is a quiet failure mode that looks like a working setup until an
order goes through and nothing happens.
Step 7: credentials and secrets management
Every API key, OAuth token and webhook secret a workflow uses gets stored encrypted in the Postgres database, using the encryption key from step four. Two practices matter here beyond the default behaviour:
Scope credentials to the workflows that need them rather than creating one shared “Shopify admin” credential reused everywhere. When a token needs rotating, a scoped credential means you know exactly which workflows to check afterward. And never put a raw API key into a workflow’s HTTP Request node as a hardcoded header; use n8n’s credential type for the service where one exists, so the key benefits from the same encryption as everything else instead of sitting in plain text inside a workflow’s JSON export.
Step 8: back up the right three things
A complete n8n backup is three components, and the step most teams get wrong is backing up one of them without the other two. Usually they back up the database on a schedule while the encryption key sits only in whoever set the server up’s head.
- The Postgres database —
pg_dumpon a schedule, stored off the server (a separate object storage bucket, not a second folder on the same VPS). - The
N8N_ENCRYPTION_KEYvalue — stored in a password manager, not a file on the same disk as the database backup. If the server is lost, you need this key to make the database dump usable at all. - Any files in the persistent volume outside the database — binary data workflows have written to disk, if any workflow does that.
Restoring a database dump without the matching encryption key produces a working-looking n8n instance where every credential fails silently the first time a workflow tries to use it. This is worse than an instance that’s obviously broken, because it surfaces as individual workflow failures over the following days rather than one clear incident.
Step 9: update without breaking live workflows
n8n ships updates on a rolling basis, and version-to-version node behaviour does occasionally change (a node that used to accept a bare string starting to require an object, for instance). Update by:
- Reading the release notes for anything touching nodes your live workflows use (HTTP Request, Shopify, webhook triggers).
- Taking a database backup immediately before the update, separate from your scheduled backups.
- Pulling the new image tag and restarting the stack in a maintenance window, not mid-trading-day for a store with live checkout automations.
- Manually triggering your two or three highest-stakes workflows once after the update, rather than waiting for their next scheduled run to discover something broke.
Pin your image to a specific version tag rather than latest. An
unattended docker compose pull on latest is how a team ends up
debugging a production incident that traces back to an update nobody
remembers approving.
Step 10: monitor execution health, not just server uptime
A server that’s up doesn’t mean workflows are succeeding. Set:
EXECUTIONS_DATA_PRUNE=true
EXECUTIONS_DATA_MAX_AGE=336
to stop execution history from growing the database indefinitely (336 hours is fourteen days; adjust to how far back you need to debug a failed run), and separately, set up an external check. Even a simple uptime monitor hitting a health-check workflow’s webhook on a schedule can alert you if executions stop happening, not just if the server stops responding. A workflow silently failing every run for three days, while the server itself reports healthy, is the actual failure mode operators miss.
How to verify your setup actually works
Before calling the migration or the build done, confirm four things in
order: a new workflow saves and executes manually without error; a
webhook-triggered workflow (register a test webhook against it) fires
within a few seconds of the trigger event, using the exact WEBHOOK_URL
configured in step six; a database backup taken that day restores into a
fresh, empty Postgres instance and n8n boots against it showing all
existing workflows; and a credential created before the restore still
decrypts and authenticates successfully afterward — this last check is the
one that catches a mismatched encryption key before it costs you a
production incident instead of a five-minute test.
Self-hosted n8n vs n8n Cloud: the ownership trade-off
| Self hosted | n8n Cloud | |
|---|---|---|
| Execution volume | Unlimited, bounded only by your server | Billed by plan tier |
| Data residency | Fully yours — database on your infrastructure | Held on n8n’s infrastructure |
| Updates | You choose the timing | Managed for you |
| Uptime | Your responsibility | n8n’s responsibility |
| Setup effort | Hours to days, one time | Minutes |
| Ongoing effort | Backups, updates, monitoring — recurring | None |
The table’s real decision point isn’t cost. Check n8n’s current pricing page for how it prices execution or task volume on the cloud plans, since that changes with releases and isn’t a number worth repeating here. What matters is whether your team has someone who’ll actually own steps eight through ten above, every month, without being asked. A self-hosted instance that nobody maintains degrades quietly: backups stop running, an update sits three versions behind until a node breaks, and the “unlimited executions” benefit becomes irrelevant next to the downtime nobody caught early. Teams that don’t have that ownership in-house, and don’t want to build it, are better served either by n8n Cloud directly or by handing the box to someone whose job is to keep it healthy.
That second option is the shape of the work we do: Pointerflow builds and runs n8n self-hosted on the client’s own VPS with unlimited executions and the instance staying yours if we part ways. A workflow engine that answers to your ownership, not a vendor’s plan tier. When the actual problem is fragile, unmonitored automation between Shopify, Klaviyo and Recharge rather than a missing tool, that’s an AI agents and automation problem, and it’s what our AI agents service is built to fix.
Sources
No external figures are quoted; this article is written from how these tools, contracts and engagements are set up and run in practice.