Self-Hosting n8n on a $6 VPS: Full Setup, Backups Included
Docker compose file, HTTPS, backups, and the memory limit that stopped my instance dying every three days. Running cost: about six dollars a month.

What this gets you
your own automation server, unlimited workflow executions, your data on your machine, for the price of a coffee per month. Setup takes about 25 minutes if you have a domain ready.
I moved off a hosted plan when my execution count made the monthly bill silly. Eight months in, total cost is the VPS and nothing else.
What you need
Related: Midjourney vs DALL-E vs Flux: A Practical Image-Tool Comparison Framework →
Related: كيف تؤتمت عملك بالذكاء الاصطناعي باستخدام n8n مجاناً (دليل عملي خطوة بخطوة 2026) →
- A VPS with 2GB RAM (1GB works but see the memory section — it will hurt)
- A domain, with an A record pointing at the server IP
- Docker and Docker Compose installed
curl -fsSL https://get.docker.com | shThe compose file
Related: 9 Free AI Coding Tools Every Developer Should Try in 2026 →
Related: 8 AI Workflow Automation Tools That Actually Save Hours a Week →
services:
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: n8n
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: n8n
volumes: [ "./pgdata:/var/lib/postgresql/data" ]n8n: image: docker.n8n.io/n8nio/n8n restart: unless-stopped depends_on: [ db ] environment: DB_TYPE: postgresdb DB_POSTGRESDB_HOST: db DB_POSTGRESDB_USER: n8n DB_POSTGRESDB_PASSWORD: ${DB_PASSWORD} N8N_HOST: ${DOMAIN} WEBHOOK_URL: https://${DOMAIN}/ N8N_PROTOCOL: https GENERIC_TIMEZONE: Europe/London N8N_ENCRYPTION_KEY: ${ENCRYPTION_KEY} EXECUTIONS_DATA_PRUNE: "true" EXECUTIONS_DATA_MAX_AGE: "336" NODE_OPTIONS: "--max-old-space-size=768" volumes: [ "./n8n:/home/node/.n8n" ]
caddy: image: caddy:2-alpine restart: unless-stopped ports: [ "80:80", "443:443" ] volumes: - ./Caddyfile:/etc/caddy/Caddyfile - ./caddy:/data
Caddyfile:
{$DOMAIN} {
reverse_proxy n8n:5678
}.env:
DOMAIN=automation.example.com
DB_PASSWORD=<long random string>
ENCRYPTION_KEY=<32+ random chars, never change this later>Then:
docker compose up -d
docker compose logs -f n8nCaddy fetches a certificate automatically. Open the domain, create the owner account, done.
Experience log: the first week's problems
Related: Sora 2 Review: OpenAI's Video Model Is Finally Useful for Real Work →
Related: MCP Servers, Explained by Someone Who Wired Five of Them Up →
It died every two or three days with no clear error. dmesg showed the OOM killer. Node's default heap grows until the box runs out. The NODE_OPTIONS: --max-old-space-size=768 line above is the fix on a 2GB box — set it to roughly 40% of total RAM. Zero crashes since.
The database grew to 4GB in three weeks. Every execution stores its full payload. EXECUTIONS_DATA_PRUNE with a 336-hour (14-day) age cap keeps it flat. If you handle large files, also set EXECUTIONS_DATA_SAVE_ON_SUCCESS: none — you rarely need the payload of a run that worked.
Webhooks returned 404 from the outside. WEBHOOK_URL was unset, so n8n generated localhost URLs and registered them with external services. Setting it correctly and re-saving each affected workflow fixed it. Re-saving matters: the URL is stored when the workflow is activated.
I lost credentials during a rebuild. Credentials are encrypted with N8N_ENCRYPTION_KEY. I regenerated the key on a fresh deploy and every stored credential became unreadable. Back that key up somewhere separate from the server, today.
Backups
Related: How to Clone Your Voice with AI in 2026 (Free and Paid Options) →
Related: Best Zero-Cost AI Tools for Automated Crypto Scalping in 2026 (Tested) →
One cron job:
#!/bin/bash
cd /opt/n8n
docker compose exec -T db pg_dump -U n8n n8n | gzip > /backups/n8n-$(date +%F).sql.gz
tar czf /backups/n8nfiles-$(date +%F).tar.gz ./n8n
find /backups -mtime +14 -delete0 3 * * * /opt/n8n/backup.shThen copy /backups off the machine — a backup that lives only on the server being backed up is not a backup. I push mine to object storage with rclone.
Restore test, which you should do once before you need it:
gunzip -c n8n-2026-05-01.sql.gz | docker compose exec -T db psql -U n8n n8nHardening in ten minutes
Related: How AI Agents Are Quietly Rewiring Enterprise Workflows in 2026 →
- Disable password SSH, key auth only.
ufw allow 22,80,443/tcpand deny everything else — never expose 5678 directly.- Turn on two-factor in n8n settings.
- Set
N8N_BLOCK_ENV_ACCESS_IN_NODE: "true"so workflow code cannot read your server environment.
Is self-hosting worth it?
Related: Twin: The 'AI Company Builder' Betting on No-Code Agent Automation →
Break-even in my case was around 3,000 executions a month. Below that, hosted is cheaper once you value the maintenance time honestly — roughly an hour a month for updates and the occasional restart. Above it, self-hosting is dramatically cheaper and you also get unrestricted access to community nodes.
FAQ
How do I update?
docker compose pull && docker compose up -d. Take a database dump first; read the release notes for breaking changes on major versions.
Can I run it without a domain? Yes over plain HTTP on an IP, but webhooks from most services require HTTPS. A domain is worth the few dollars a year.
Is 1GB RAM viable? For a handful of simple workflows, yes with a 512MB heap cap. Anything with large payloads or parallel executions will thrash.
What it actually cost me, month by month
| Item | Monthly cost | Note |
|---|---|---|
| VPS, 2 vCPU / 2GB RAM | $6 | ran 14 active workflows comfortably |
| Domain + TLS | ~$1 | Let's Encrypt is free, domain amortised |
| Object storage for backups | $1 | weekly database dumps |
| Model API calls | $3-9 | varies; filtering first cut this by ~70% |
| Total | $11-17 | the equivalent hosted plan quoted me far more |
Setup log: the four things that bit me
- Webhooks returned 404 behind the reverse proxy. Cause:
WEBHOOK_URLstill pointed at localhost. Set it to the public HTTPS URL before testing anything. - Workflows vanished after a container rebuild. No volume mounted for the database. Mount it before you build your first workflow, not after.
- Disk filled in six weeks. Execution logs.
EXECUTIONS_DATA_MAX_AGE=336(14 days) fixed it permanently. - A silent failure ran unnoticed for three days. An expired OAuth token. Every workflow now has an error branch that pings me on failure — treat it as mandatory, not optional.
New to n8n entirely? Start with the full step-by-step build: automating your work with n8n.
Related Articles
مقالات ذات صلة — تابع القراءة داخل الموقع
مواضيع مقترحة · Suggested Topics
استكشف مواضيع ومحاور ذات صلة بهذا المقال — روابط داخلية لتعميق قراءتك.
The Daily Pulse
Newsletter delivery is not connected yet. This form only saves your address in this browser; no email is sent.
Get concise, source-linked technology notes without the hype.




